Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Support for verifiable presentations (proofs) from identities and accounts

## [1.16.1] - 2025-12-05

### Fixed
Expand Down
10 changes: 5 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ android {
// This project uses semantic versioning
// https://semver.org/
def versionMajor = 1
def versionMinor = 16
def versionPatch = 1
def versionMeta = ""
def versionCodeIncremental = 1487
def versionMinor = 17
def versionPatch = 0
def versionMeta = "-qa.1"
def versionCodeIncremental = 1490

defaultConfig {
applicationId "com.pioneeringtechventures.wallet"
Expand Down Expand Up @@ -244,7 +244,7 @@ allprojects {

dependencies {

implementation("com.concordium.sdk:concordium-android-sdk:11.1.0") {
implementation("com.concordium.sdk:concordium-android-sdk:11.2.1") {
exclude group: "org.bouncycastle"
exclude group: "net.jcip"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.concordium.wallet.data.cryptolib

import com.concordium.wallet.data.room.Identity

/**
* Decrypted contents of [Identity.privateIdObjectDataEncrypted],
* for file-based identities.
*/
class PrivateIdObjectData(
val randomness: String,
val aci: Aci,
) {
class Aci(
val prfKey: String,
val credentialHolderInformation: CredentialHolderInformation,
) {
class CredentialHolderInformation(
val idCredSecret: String,
)
}
}
14 changes: 12 additions & 2 deletions app/src/main/java/com/concordium/wallet/data/model/ArsInfo.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package com.concordium.wallet.data.model

import com.concordium.sdk.responses.blocksummary.updates.queues.AnonymityRevokerInfo
import com.concordium.sdk.serializing.JsonMapper
import com.concordium.wallet.App
import java.io.Serializable

data class ArsInfo(
val arIdentity: Int,
val arPublicKey: String,
val arDescription: ArDescription
) : Serializable
val arDescription: ArDescription,
) : Serializable {

fun toSdkAnonymityRevokerInfo(): AnonymityRevokerInfo =
JsonMapper.INSTANCE.readValue(
App.appCore.gson.toJson(this),
AnonymityRevokerInfo::class.java
)
}
15 changes: 13 additions & 2 deletions app/src/main/java/com/concordium/wallet/data/model/GlobalParams.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
package com.concordium.wallet.data.model

import com.concordium.sdk.crypto.bulletproof.BulletproofGenerators
import com.concordium.sdk.crypto.pedersencommitment.PedersenCommitmentKey
import com.concordium.sdk.responses.cryptographicparameters.CryptographicParameters
import java.io.Serializable

data class GlobalParams(
val onChainCommitmentKey: String,
val bulletproofGenerators: String,
val genesisString: String
) : Serializable
val genesisString: String,
) : Serializable {

fun toSdkCryptographicParameters(): CryptographicParameters =
CryptographicParameters.builder()
.genesisString(genesisString)
.bulletproofGenerators(BulletproofGenerators.from(bulletproofGenerators))
.onChainCommitmentKey(PedersenCommitmentKey.from(onChainCommitmentKey))
.build()
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.concordium.wallet.data.model

import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject
import com.concordium.sdk.serializing.JsonMapper
import com.concordium.wallet.App
import com.concordium.wallet.core.gson.RawJsonTypeAdapter
import com.google.gson.annotations.JsonAdapter
import java.io.Serializable
Expand All @@ -8,5 +11,18 @@ data class IdentityObject(
val attributeList: AttributeList,
val preIdentityObject: PreIdentityObject,
@JsonAdapter(RawJsonTypeAdapter::class)
val signature: RawJson
) : Serializable
val signature: RawJson,
) : Serializable {

@Transient
private var memoizedSdkIdentityObject: IdentityObject? = null
fun toSdkIdentityObject(): IdentityObject =
memoizedSdkIdentityObject
?: JsonMapper
.INSTANCE
.readValue(
App.appCore.gson.toJson(this),
IdentityObject::class.java
)
.also { memoizedSdkIdentityObject = it }
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package com.concordium.wallet.data.model

import com.concordium.sdk.responses.blocksummary.updates.queues.IdentityProviderInfo
import com.concordium.sdk.serializing.JsonMapper
import com.concordium.wallet.App
import java.io.Serializable

data class IdentityProviderInfo(
val ipIdentity: Int,
val ipDescription: IdentityProviderDescription,
val ipVerifyKey: String,
val ipCdiVerifyKey: String
) : Serializable
val ipCdiVerifyKey: String,
) : Serializable {

fun toSdkIdentityProviderInfo(): IdentityProviderInfo =
JsonMapper.INSTANCE.readValue(
App.appCore.gson.toJson(this),
IdentityProviderInfo::class.java
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ data class SubmissionStatusResponse(
val blockHashes: List<String>?,
val rejectReason: String?,
val encryptedAmount: String?,
val aggregatedIndex: Int?
val aggregatedIndex: Int?,
val registeredData: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.concordium.wallet.ui.walletconnect

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.core.view.isVisible
import com.concordium.wallet.data.room.Identity
import com.concordium.wallet.databinding.AccountInfoRowBinding

class ChooseIdentityListAdapter(
private val context: Context,
private var arrayList: List<Identity>,
) : BaseAdapter() {
private var clickListener: ((Identity) -> Unit)? = null

fun setOnClickListener(listener: (Identity) -> Unit) {
this.clickListener = listener
}

override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val binding: AccountInfoRowBinding
if (convertView == null) {
binding = AccountInfoRowBinding.inflate(LayoutInflater.from(context), parent, false)
binding.root.tag = binding
} else {
binding = convertView.tag as AccountInfoRowBinding
}

val identity = arrayList[position]

with(binding) {
accAddress.text = identity.name
accBalance.isVisible = false
accIdentity.isVisible = false
accBalanceAtDisposal.isVisible = false

root.setOnClickListener {
clickListener?.invoke(identity)
}
}

return binding.root
}

override fun getCount(): Int = arrayList.size

override fun getItem(position: Int): Any? {
return null
}

override fun getItemId(position: Int): Long {
return 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.concordium.sdk.crypto.wallet.web3Id.Statement.RequestStatement
import com.concordium.wallet.data.room.Account
import com.concordium.wallet.data.room.Identity
import com.concordium.wallet.databinding.IdentityProofContainerBinding
import com.concordium.wallet.util.Log

class CredentialStatementAdapter(
private val statements: List<RequestStatement>,
private val accounts: List<Account>,
private val getIdentity: (account: Account) -> Identity?,
private val onChangeAccountClicked: (index: Int) -> Unit
private val claims: List<IdentityProofRequestClaims>,
private val onChangeAccountClicked: (index: Int) -> Unit,
private val onIdentityChangeClicked: (index: Int) -> Unit,
) : RecyclerView.Adapter<CredentialStatementAdapter.ViewHolder>() {
class ViewHolder(val containerBinding: IdentityProofContainerBinding) :
RecyclerView.ViewHolder(containerBinding.root)
Expand All @@ -29,27 +24,38 @@ class CredentialStatementAdapter(
}

override fun getItemCount(): Int {
return statements.size
return claims.size
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val account = accounts[position]
val identity = getIdentity(account)
val selectedCredential = claims[position].selectedCredential
val identity = selectedCredential.identity

if (identity == null) {
Log.e("Identity is not available for account ${account.address}")
return
}
holder.containerBinding.statements.setStatement(claims[position], identity)

holder.containerBinding.statements.setStatement(statements[position], identity)
with(holder.containerBinding.selectedAccountInclude) {
accAddress.text = account.getAccountName()
accBalance.isVisible = false
accIdentity.isVisible = true
accIdentity.text = identity.name
}
holder.containerBinding.selectedAccountIncludeContainer.setOnClickListener {
onChangeAccountClicked(position)
when (selectedCredential) {
is IdentityProofRequestSelectedCredential.Account -> {
with(holder.containerBinding.selectedCredentialInclude) {
accAddress.text = selectedCredential.account.getAccountName()
accBalance.isVisible = false
accIdentity.isVisible = true
accIdentity.text = identity.name
}
holder.containerBinding.selectedCredentialInclude.root.setOnClickListener {
onChangeAccountClicked(position)
}
}

is IdentityProofRequestSelectedCredential.Identity -> {
with(holder.containerBinding.selectedCredentialInclude) {
accAddress.text = identity.name
accBalance.isVisible = false
accIdentity.isVisible = false
}
holder.containerBinding.selectedCredentialInclude.root.setOnClickListener{
onIdentityChangeClicked(position)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,11 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import com.concordium.sdk.crypto.wallet.identityobject.AttributeList
import com.concordium.sdk.crypto.wallet.identityobject.IdentityObject
import com.concordium.sdk.crypto.wallet.web3Id.CredentialAttribute
import com.concordium.sdk.crypto.wallet.web3Id.Statement.AtomicStatement
import com.concordium.sdk.crypto.wallet.web3Id.Statement.MembershipStatement
import com.concordium.sdk.crypto.wallet.web3Id.Statement.NonMembershipStatement
import com.concordium.sdk.crypto.wallet.web3Id.Statement.RangeStatement
import com.concordium.sdk.crypto.wallet.web3Id.Statement.RequestStatement
import com.concordium.sdk.crypto.wallet.web3Id.Statement.RevealStatement
import com.concordium.sdk.crypto.wallet.web3Id.Statement.SetStatement
import com.concordium.sdk.responses.accountinfo.credential.AttributeType
Expand All @@ -39,20 +36,23 @@ class DisplayStatements(context: Context, attrs: AttributeSet): LinearLayout(con
addView(binding.root)
}

fun setStatement(request: RequestStatement, identity: Identity) {
fun setStatement(request: IdentityProofRequestClaims, identity: Identity) {
binding.revealStatements.revealLines.removeAllViews()
binding.secretStatements.secretLines.removeAllViews()

val secretStatements = request.statement.filterNot { it is RevealStatement }
val revealStatements = request.statement.filterIsInstance<RevealStatement>()
val secretStatements = request.statements.filterNot { it is RevealStatement }
val revealStatements = request.statements.filterIsInstance<RevealStatement>()

if (secretStatements.isEmpty()) {
// If there are no reveal statements, then don't show the reveal box
binding.secretStatements.root.visibility = GONE
} else {
binding.secretStatements.root.visibility = VISIBLE
secretStatements.forEach {
binding.secretStatements.secretLines.addView(getSecretStatement(it, it.canBeProvedBy((getIdentityObject(identity)))))
binding.secretStatements.secretLines.addView(getSecretStatement(
it,
it.canBeProvedBy(identity.identityObject!!.toSdkIdentityObject())
))
}
}

Expand Down Expand Up @@ -325,14 +325,3 @@ class DisplayStatements(context: Context, attrs: AttributeSet): LinearLayout(con
val EU_MEMBERS = listOf("AT", "BE", "BG", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "HR")
}
}


/**
* Get an IdentityObject compatible with the Concordium SDK methods.
* N.B. Only the attributeList is populated, the remaining fields are null
*/
fun getIdentityObject(identity: Identity): IdentityObject {
val identityObject = identity.identityObject!!
val attributes = AttributeList.builder().chosenAttributes(identityObject.attributeList.chosenAttributes.mapKeys { AttributeType.fromJSON(it.key) }).build()
return IdentityObject.builder().attributeList(attributes).build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.concordium.wallet.ui.walletconnect

import com.concordium.sdk.crypto.wallet.web3Id.Statement.AtomicStatement

/**
* Some statements about identity which can be proven
* by either account, identity, or both.
*/
data class IdentityProofRequestClaims(
val statements: List<AtomicStatement>,
val selectedCredential: IdentityProofRequestSelectedCredential,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.concordium.wallet.ui.walletconnect

sealed interface IdentityProofRequestSelectedCredential {

val identity: com.concordium.wallet.data.room.Identity

class Account(
val account: com.concordium.wallet.data.room.Account,
override val identity: com.concordium.wallet.data.room.Identity,
) :
IdentityProofRequestSelectedCredential

class Identity(override val identity: com.concordium.wallet.data.room.Identity) :
IdentityProofRequestSelectedCredential
}
Loading