Skip to content

Commit

Permalink
Merge pull request #1921 from dedis/work-fe2-maxime-deterministic-use…
Browse files Browse the repository at this point in the history
…rname

Displaying Deterministic Username for a given Token
  • Loading branch information
Kaz-ookid authored Jun 27, 2024
2 parents d3c68fe + ed05d3a commit d2a394e
Show file tree
Hide file tree
Showing 25 changed files with 381 additions and 85 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.github.dedis.popstellar.model.objects.security

import com.github.dedis.popstellar.model.Immutable
import com.github.dedis.popstellar.utility.GeneralUtils
import com.google.crypto.tink.PublicKeyVerify
import com.google.crypto.tink.subtle.Ed25519Verify
import java.security.GeneralSecurityException
Expand All @@ -14,13 +15,16 @@ import timber.log.Timber
@Immutable
class PublicKey : Base64URLData {
private val verifier: PublicKeyVerify
@Transient private var label: String

constructor(data: ByteArray) : super(data) {
verifier = Ed25519Verify(data)
label = GeneralUtils.generateUsernameFromBase64(this.encoded)
}

constructor(data: String) : super(data) {
verifier = Ed25519Verify(this.data)
label = GeneralUtils.generateUsernameFromBase64(this.encoded)
}

fun verify(signature: Signature, data: Base64URLData): Boolean {
Expand All @@ -33,6 +37,14 @@ class PublicKey : Base64URLData {
}
}

/**
* Function that return the username of the public key The username is deterministic and is
* computed from the hash of the public key
*/
fun getLabel(): String {
return label
}

/**
* Function that compute the hash of a public key
*
Expand All @@ -52,5 +64,13 @@ class PublicKey : Base64URLData {

companion object {
private val TAG = PublicKey::class.java.simpleName

fun findPublicKeyFromUsername(
username: String,
publicKeys: List<PublicKey>,
default: PublicKey
): PublicKey {
return publicKeys.firstOrNull { it.getLabel() == username } ?: default
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment
import com.github.dedis.popstellar.R
import com.github.dedis.popstellar.databinding.DigitalCashIssueFragmentBinding
import com.github.dedis.popstellar.model.objects.security.PublicKey
import com.github.dedis.popstellar.model.objects.security.PublicKey.Companion.findPublicKeyFromUsername
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.obtainDigitalCashViewModel
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.obtainViewModel
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.setCurrentFragment
Expand Down Expand Up @@ -74,14 +75,19 @@ class DigitalCashIssueFragment : Fragment() {
private fun issueCoins() {
/*Take the amount entered by the user*/
val currentAmount = binding.digitalCashIssueAmount.text.toString()
val currentPublicKeySelected = binding.digitalCashIssueSpinner.editText!!.text.toString()
val currentPublicKeySelected =
findPublicKeyFromUsername(
binding.digitalCashIssueSpinner.editText!!.text.toString(),
digitalCashViewModel.attendeesFromTheRollCallList,
digitalCashViewModel.validToken.publicKey)
val radioGroup = binding.digitalCashIssueSelect.checkedRadioButtonId

if (digitalCashViewModel.canPerformTransaction(
currentAmount, currentPublicKeySelected, radioGroup)) {
currentAmount, currentPublicKeySelected.encoded, radioGroup)) {
try {
val issueMap =
computeMapForPostTransaction(currentAmount, currentPublicKeySelected, radioGroup)
computeMapForPostTransaction(
currentAmount, currentPublicKeySelected.encoded, radioGroup)
if (issueMap.isEmpty()) {
displayToast(radioGroup)
} else {
Expand Down Expand Up @@ -165,7 +171,7 @@ class DigitalCashIssueFragment : Fragment() {
/* Roll Call attendees to which we can send*/
var myArray: List<String>
try {
myArray = digitalCashViewModel.attendeesFromTheRollCallList
myArray = digitalCashViewModel.attendeesFromTheRollCallList.map { it.getLabel() }
} catch (e: NoRollCallException) {
Timber.tag(TAG).e(getString(R.string.error_no_rollcall_closed_in_LAO))
Toast.makeText(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment
import com.github.dedis.popstellar.R
import com.github.dedis.popstellar.SingleEvent
import com.github.dedis.popstellar.databinding.DigitalCashReceiptFragmentBinding
import com.github.dedis.popstellar.model.objects.security.PublicKey
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.addBackNavigationCallback
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.obtainDigitalCashViewModel
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.obtainViewModel
Expand Down Expand Up @@ -51,11 +52,12 @@ class DigitalCashReceiptFragment : Fragment() {
}

digitalCashViewModel.getUpdateReceiptAddressEvent().observe(viewLifecycleOwner) {
stringEvent: SingleEvent<String> ->
val address = stringEvent.contentIfNotHandled
if (address != null) {
stringEvent: SingleEvent<PublicKey> ->
val pk = stringEvent.contentIfNotHandled
if (pk != null) {
binding.digitalCashReceiptBeneficiary.text =
String.format(resources.getString(R.string.digital_cash_beneficiary_address), address)
String.format(
resources.getString(R.string.digital_cash_beneficiary_address), pk.getLabel())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class DigitalCashReceiveFragment : Fragment() {
val token = digitalCashViewModel.validToken
val publicKey = token.publicKey

binding.digitalCashReceiveAddress.text = publicKey.encoded
binding.digitalCashReceiveAddress.text = publicKey.getLabel()

val tokenData = PopTokenData(token.publicKey)
val myBitmap =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.github.dedis.popstellar.R
import com.github.dedis.popstellar.SingleEvent
import com.github.dedis.popstellar.databinding.DigitalCashSendFragmentBinding
import com.github.dedis.popstellar.model.objects.security.PublicKey
import com.github.dedis.popstellar.model.objects.security.PublicKey.Companion.findPublicKeyFromUsername
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.obtainDigitalCashViewModel
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.obtainViewModel
import com.github.dedis.popstellar.ui.lao.LaoActivity.Companion.setCurrentFragment
Expand Down Expand Up @@ -60,15 +61,20 @@ class DigitalCashSendFragment : Fragment() {
val event = booleanEvent.contentIfNotHandled
if (event != null) {
val currentAmount = binding.digitalCashSendAmount.text.toString()
val currentPublicKeySelected = binding.digitalCashSendSpinner.editText?.text.toString()
val currentPublicKeySelected =
findPublicKeyFromUsername(
binding.digitalCashSendSpinner.editText?.text.toString(),
digitalCashViewModel.attendeesFromTheRollCallList,
digitalCashViewModel.validToken.publicKey)

if (digitalCashViewModel.canPerformTransaction(
currentAmount, currentPublicKeySelected, -1)) {
currentAmount, currentPublicKeySelected.encoded, -1)) {
try {
val token = digitalCashViewModel.validToken
if (canPostTransaction(token.publicKey, currentAmount.toInt())) {
laoViewModel.addDisposable(
postTransaction(Collections.singletonMap(currentPublicKeySelected, currentAmount))
postTransaction(
Collections.singletonMap(currentPublicKeySelected.encoded, currentAmount))
.subscribe(
{
digitalCashViewModel.updateReceiptAddressEvent(currentPublicKeySelected)
Expand Down Expand Up @@ -124,7 +130,8 @@ class DigitalCashSendFragment : Fragment() {
/* Roll Call attendees to which we can send */
var myArray: MutableList<String>
try {
myArray = digitalCashViewModel.attendeesFromTheRollCallList.toMutableList()
myArray =
digitalCashViewModel.attendeesFromTheRollCallList.map { it.getLabel() }.toMutableList()
} catch (e: NoRollCallException) {
Timber.tag(TAG).d(e)
Toast.makeText(
Expand Down Expand Up @@ -155,7 +162,7 @@ class DigitalCashSendFragment : Fragment() {
*/
private fun removeOwnToken(members: MutableList<String>) {
try {
members.remove(digitalCashViewModel.validToken.publicKey.encoded)
members.remove(digitalCashViewModel.validToken.publicKey.getLabel())
} catch (e: KeyException) {
Timber.tag(TAG).e(e, resources.getString(R.string.error_retrieve_own_token))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import io.reactivex.Single
import java.nio.charset.StandardCharsets
import java.security.GeneralSecurityException
import java.util.Collections
import java.util.stream.Collectors
import javax.inject.Inject
import timber.log.Timber

Expand Down Expand Up @@ -69,7 +68,7 @@ constructor(
private val postTransactionEvent = MutableLiveData<SingleEvent<Boolean>>()

/* Update the receipt after sending a transaction */
private val updateReceiptAddressEvent = MutableLiveData<SingleEvent<String>>()
private val updateReceiptAddressEvent = MutableLiveData<SingleEvent<PublicKey>>()
private val updateReceiptAmountEvent = MutableLiveData<SingleEvent<String>>()

fun getPostTransactionEvent(): LiveData<SingleEvent<Boolean>> {
Expand All @@ -80,11 +79,11 @@ constructor(
postTransactionEvent.postValue(SingleEvent(true))
}

fun getUpdateReceiptAddressEvent(): LiveData<SingleEvent<String>> {
fun getUpdateReceiptAddressEvent(): LiveData<SingleEvent<PublicKey>> {
return updateReceiptAddressEvent
}

fun updateReceiptAddressEvent(address: String?) {
fun updateReceiptAddressEvent(address: PublicKey?) {
updateReceiptAddressEvent.postValue(SingleEvent(address))
}

Expand Down Expand Up @@ -237,9 +236,8 @@ constructor(
get() = lao.organizer

@get:Throws(NoRollCallException::class)
val attendeesFromTheRollCallList: List<String>
get() =
attendeesFromLastRollCall.stream().map(Base64URLData::encoded).collect(Collectors.toList())
val attendeesFromTheRollCallList: List<PublicKey>
get() = attendeesFromLastRollCall.toList()

@get:Throws(UnknownLaoException::class)
val lao: LaoView
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ class HistoryListAdapter(
holder.transactionIdValue.text = transactionId
holder.transactionProvenanceTitle.setText(
if (element.isSender) R.string.digital_cash_to else R.string.digital_cash_from)
holder.transactionProvenanceValue.text = element.senderOrReceiver
holder.transactionProvenanceValue.text =
if (element.senderOrReceiver.encoded == viewModel.organizer.encoded)
element.senderOrReceiver.encoded + " (organizer)"
else element.senderOrReceiver.getLabel()

val listener =
View.OnClickListener {
Expand Down Expand Up @@ -96,7 +99,6 @@ class HistoryListAdapter(
// To know if we are in input or not. We assume that no two different person
val isSender = transactionObject.isSender(ownKey)
val isIssuance = transactionObject.isCoinBaseTransaction

transactionHistoryElements.addAll(
transactionObject.outputs
.stream() // If we are in input, we want all output except us. If we are not in input,
Expand All @@ -108,8 +110,11 @@ class HistoryListAdapter(
}
.map { outputObject: OutputObject ->
TransactionHistoryElement(
if (isSender) outputObject.pubKeyHash
else transactionObject.inputs[0].pubKey.encoded,
// TODO : was previously outputObject.pubKeyHash, but was wrong (hash is smaller
// than 32 bytes and is clearly not the receivers public key)
// I set it to ownKey so that it works for now, but should actually show the
// public key of the receiver | Maxime @Kaz-ookid 06.2025
if (isSender) ownKey else PublicKey(transactionObject.inputs[0].pubKey.encoded),
outputObject.value.toString(),
transactionObject.transactionId,
!isIssuance && isSender)
Expand All @@ -125,9 +130,9 @@ class HistoryListAdapter(
val transactionTypeTitle: TextView = itemView.findViewById(R.id.history_transaction_type_title)
val transactionTypeValue: TextView = itemView.findViewById(R.id.history_transaction_type_value)
val transactionProvenanceTitle: TextView =
itemView.findViewById(R.id.history_transaction_provenance_value)
val transactionProvenanceValue: TextView =
itemView.findViewById(R.id.history_transaction_provenance_title)
val transactionProvenanceValue: TextView =
itemView.findViewById(R.id.history_transaction_provenance_value)
val transactionIdValue: TextView =
itemView.findViewById(R.id.history_transaction_transaction_id_value)
val detailLayout: ConstraintLayout =
Expand All @@ -136,14 +141,14 @@ class HistoryListAdapter(
}

private class TransactionHistoryElement(
val senderOrReceiver: String,
val senderOrReceiver: PublicKey,
val value: String,
val id: String,
val isSender: Boolean
) {

override fun toString(): String {
return "TransactionHistoryElement{senderOrReceiver='$senderOrReceiver', value='$value', " +
return "TransactionHistoryElement{senderOrReceiverHash='${senderOrReceiver.encoded}', senderOrReceiverUsername='${senderOrReceiver.getLabel()}',value='$value', " +
"id='$id', isSender=$isSender}"
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,64 @@
package com.github.dedis.popstellar.ui.lao.event.rollcall

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.github.dedis.popstellar.R
import com.github.dedis.popstellar.model.objects.security.PoPToken
import com.github.dedis.popstellar.model.objects.security.PublicKey

class RollCallArrayAdapter(
private val context: Context,
private val layout: Int,
private val attendeesList: List<String>,
private val attendeesList: List<PublicKey>,
private val myToken: PoPToken?,
private val fragment: RollCallFragment
) : ArrayAdapter<String>(context, layout, attendeesList) {
) : ArrayAdapter<PublicKey>(context, layout, attendeesList) {

init {
fragment.isAttendeeListSorted(attendeesList, context)
fragment.isAttendeeListSorted(attendeesList.map { it.encoded }, context)
}

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = super.getView(position, convertView, parent)
val view: View
val holder: ViewHolder

// highlights our token in the list
val currentToken = getItem(position)
if (myToken != null && currentToken == myToken.publicKey.encoded) {
val colorAccent = ContextCompat.getColor(context, R.color.colorAccent)
(view as TextView).setTextColor(colorAccent)
if (convertView == null) {
view = LayoutInflater.from(context).inflate(R.layout.list_item_attendee, parent, false)
holder = ViewHolder(view)
view.tag = holder
} else {
view = convertView
holder = view.tag as ViewHolder
}

val publicKey = getItem(position)
if (publicKey != null) {
holder.usernameTextView.text = publicKey.getLabel()
holder.hashTextView.text = publicKey.encoded

// Set the default color
val defaultColor = ContextCompat.getColor(context, R.color.textOnBackground)
holder.usernameTextView.setTextColor(defaultColor)
holder.hashTextView.setTextColor(defaultColor)

// highlights our token in the list
if (myToken != null && publicKey.encoded == myToken.publicKey.encoded) {
val colorAccent = ContextCompat.getColor(context, R.color.colorAccent)
holder.usernameTextView.setTextColor(colorAccent)
holder.hashTextView.setTextColor(colorAccent)
}
}

return view
}

private class ViewHolder(view: View) {
val usernameTextView: TextView = view.findViewById(R.id.username_text_view)
val hashTextView: TextView = view.findViewById(R.id.hash_text_view)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,16 +248,15 @@ class RollCallFragment : AbstractEventFragment {
binding.rollCallAttendeesText.visibility = visibility
binding.listViewAttendees.visibility = visibility

var attendeesList: List<String>? = null
var attendeesList: List<PublicKey>? = null
if (isOrganizer && rollCall.isOpen) {
// Show the list of all time scanned attendees if the roll call is opened
// and the user is the organizer
attendeesList =
rollCallViewModel
.getAttendees()
.stream()
.map(PublicKey::encoded)
.sorted(compareBy(String::toString))
.sorted(compareBy { it.encoded })
.collect(Collectors.toList())

binding.rollCallAttendeesText.text =
Expand All @@ -266,7 +265,7 @@ class RollCallFragment : AbstractEventFragment {
rollCallViewModel.getAttendees().size)
} else if (rollCall.isClosed) {
val orderedAttendees: MutableSet<PublicKey> = LinkedHashSet(rollCall.attendees)
attendeesList = orderedAttendees.stream().map(PublicKey::encoded).collect(Collectors.toList())
attendeesList = orderedAttendees.stream().collect(Collectors.toList())

// Show the list of attendees if the roll call has ended
binding.rollCallAttendeesText.text =
Expand Down Expand Up @@ -295,10 +294,12 @@ class RollCallFragment : AbstractEventFragment {
private fun retrieveAndDisplayPublicKey() {
val popToken = popToken ?: return
val pk = popToken.publicKey.encoded
val pkUsername = popToken.publicKey.getLabel()
Timber.tag(TAG).d("key displayed is %s", pk)

// Set the QR visible only if the rollcall is opened and the user isn't the organizer
if (rollCall.isOpen) {
binding.rollCallPopTokenUsername.text = pkUsername
binding.rollCallPopTokenText.text = pk
binding.rollCallPkQrCode.visibility = View.VISIBLE
binding.rollCallPopTokenText.visibility = View.VISIBLE
Expand Down
Loading

0 comments on commit d2a394e

Please sign in to comment.