Skip to content

Commit

Permalink
Add travelRuleFormat field to the payerdata compliance
Browse files Browse the repository at this point in the history
  • Loading branch information
jklein24 committed Oct 21, 2023
1 parent ef5ddaf commit cc49bef
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 2 deletions.
1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions uma-sdk/src/commonMain/kotlin/me/uma/UmaProtocolHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ class UmaProtocolHelper @JvmOverloads constructor(
* compliance provider, this will be used to pre-screen the sender's UTXOs for compliance purposes.
* @param payerName The name of the sender (optional).
* @param payerEmail The email of the sender (optional).
* @param travelRuleFormat An optional standardized format of the travel rule information (e.g. IVMS). Null
* indicates raw json or a custom format.
* @return The [PayRequest] that should be sent to the receiver.
*/
@JvmOverloads
Expand All @@ -260,6 +262,7 @@ class UmaProtocolHelper @JvmOverloads constructor(
payerNodePubKey: String? = null,
payerName: String? = null,
payerEmail: String? = null,
travelRuleFormat: TravelRuleFormat? = null,
): PayRequest {
val compliancePayerData = getSignedCompliancePayerData(
receiverEncryptionPubKey,
Expand All @@ -270,6 +273,7 @@ class UmaProtocolHelper @JvmOverloads constructor(
payerUtxos,
payerNodePubKey,
utxoCallback,
travelRuleFormat
)
val payerData = PayerData(
identifier = payerIdentifier,
Expand All @@ -293,6 +297,7 @@ class UmaProtocolHelper @JvmOverloads constructor(
payerUtxos: List<String>?,
payerNodePubKey: String?,
utxoCallback: String,
travelRuleFormat: TravelRuleFormat?,
): CompliancePayerData {
val nonce = generateNonce()
val timestamp = System.currentTimeMillis() / 1000
Expand All @@ -305,6 +310,7 @@ class UmaProtocolHelper @JvmOverloads constructor(
signatureNonce = nonce,
signatureTimestamp = timestamp,
utxoCallback = utxoCallback,
travelRuleFormat = travelRuleFormat,
)
val signablePayload = "$payerIdentifier|$nonce|$timestamp".encodeToByteArray()
val signature = signPayload(signablePayload, sendingVaspPrivateKey)
Expand Down
41 changes: 41 additions & 0 deletions uma-sdk/src/commonMain/kotlin/me/uma/protocol/PayRequest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package me.uma.protocol

import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encodeToString
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.Json

/**
Expand Down Expand Up @@ -50,6 +55,8 @@ data class PayerData @JvmOverloads constructor(
* @property signature The signature of the sender on the signable payload.
* @property signatureNonce The nonce used in the signature.
* @property signatureTimestamp The timestamp used in the signature.
* @property travelRuleFormat An optional standardized format of the travel rule information (e.g. IVMS). Null
* indicates raw json or a custom format.
*/
@Serializable
data class CompliancePayerData(
Expand All @@ -61,6 +68,40 @@ data class CompliancePayerData(
val signature: String,
val signatureNonce: String,
val signatureTimestamp: Long,
val travelRuleFormat: TravelRuleFormat? = null,
) {
fun signedWith(signature: String) = copy(signature = signature)
}

/**
* A standardized format of the travel rule information.
*/
@Serializable(with = TravelRuleFormatSerializer::class)
data class TravelRuleFormat(
/** The type of the travel rule format (e.g. IVMS). */
val type: String,
/** The version of the travel rule format (e.g. 1.0). */
val version: String?,
)

/**
* Serializes the TravelRuleFormat to string in the format of "type@version". If there's no version, it will be
* serialized as "type".
*/
class TravelRuleFormatSerializer : KSerializer<TravelRuleFormat> {
override val descriptor = PrimitiveSerialDescriptor("TravelRuleFormat", PrimitiveKind.STRING)

override fun deserialize(decoder: Decoder): TravelRuleFormat {
val value = decoder.decodeString()
if (!value.contains("@")) {
return TravelRuleFormat(value, null)
}
val parts = value.split("@")
return TravelRuleFormat(parts[0], parts.getOrNull(1))
}

override fun serialize(encoder: Encoder, value: TravelRuleFormat) {
encoder.encodeString("${value.type}${value.version?.let { "@$it" } ?: ""}")
}
}

35 changes: 34 additions & 1 deletion uma-sdk/src/commonTest/kotlin/me/uma/UmaTests.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package me.uma

import me.uma.crypto.Secp256k1
import me.uma.protocol.KycStatus
import me.uma.protocol.PayerDataOptions
import me.uma.protocol.TravelRuleFormat
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import me.uma.protocol.PayerDataOptions

@OptIn(ExperimentalCoroutinesApi::class)
class UmaTests {
val keys = Secp256k1.generateKeyPair()

@Test
fun `test serialize PayerDataOptions`() = runTest {
val payerDataOptions = PayerDataOptions(
Expand All @@ -22,4 +28,31 @@ class UmaTests {
Json.decodeFromString(PayerDataOptions.serializer(), json),
)
}

@OptIn(ExperimentalStdlibApi::class)
@Test
fun `test create and parse payreq`() = runTest {
val travelRuleInfo = "travel rule info"
val payreq = UmaProtocolHelper().getPayRequest(
receiverEncryptionPubKey = keys.publicKey,
sendingVaspPrivateKey = keys.privateKey,
currencyCode = "USD",
amount = 100,
payerIdentifier = "[email protected]",
payerKycStatus = KycStatus.VERIFIED,
utxoCallback = "https://example.com/utxo",
travelRuleInfo = "travel rule info",
travelRuleFormat = TravelRuleFormat("someFormat", "1.0"),
)
val json = payreq.toJson()
val decodedPayReq = UmaProtocolHelper().parseAsPayRequest(json)
assertEquals(payreq, decodedPayReq)

val encryptedTravelRuleInfo =
decodedPayReq.payerData.compliance?.travelRuleInfo ?: fail("travel rule info not found")
assertEquals(
travelRuleInfo,
String(Secp256k1.decryptEcies(encryptedTravelRuleInfo.hexToByteArray(), keys.privateKey)),
)
}
}

0 comments on commit cc49bef

Please sign in to comment.