Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Displaying Deterministic Username for a given Token #1921

Merged
merged 23 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
22811d7
finding some way to generate usernames
Kaz-ookid Jun 11, 2024
f57e54e
username display in chirps feed
Kaz-ookid Jun 12, 2024
79224d1
username display in DigitalCash : transactions history, issuing, send…
Kaz-ookid Jun 12, 2024
e590d64
PublicKey class now holding getUsername + username deterministic gene…
Kaz-ookid Jun 12, 2024
6833acb
Refactored RollCall attendee list UI to show username above the hash
Kaz-ookid Jun 12, 2024
f58b83e
removed temporary tests
Kaz-ookid Jun 12, 2024
287c210
username in token fragment
Kaz-ookid Jun 12, 2024
fea4ea3
documentations + fmt
Kaz-ookid Jun 12, 2024
07564de
removed debug logs
Kaz-ookid Jun 12, 2024
e48f042
Merge branch 'master' into work-fe2-maxime-deterministic-username
Kaz-ookid Jun 12, 2024
1fb3c9d
merged master + fmt
Kaz-ookid Jun 12, 2024
d680f3e
fixed a crash
Kaz-ookid Jun 12, 2024
afd9068
fixed digitalcash issue crashing when selecting our own token
Kaz-ookid Jun 23, 2024
d1a979e
updated RollCall QR, now showing username + pk hash
Kaz-ookid Jun 23, 2024
bc0ecbe
fixed tests failing after refactor
Kaz-ookid Jun 25, 2024
1b1d316
adding tests for username generation + DigitalCash coverage
Kaz-ookid Jun 25, 2024
8da40c8
adding tests for username generation + DigitalCash coverage
Kaz-ookid Jun 25, 2024
d073a48
Merge remote-tracking branch 'origin/work-fe2-maxime-deterministic-us…
Kaz-ookid Jun 25, 2024
38eb0fc
tests for coins issuing elements
Kaz-ookid Jun 25, 2024
9ef49f1
addressing comments :
Kaz-ookid Jun 26, 2024
432a370
addressing comments :
Kaz-ookid Jun 26, 2024
ee37ad4
Merge remote-tracking branch 'origin/work-fe2-maxime-deterministic-us…
Kaz-ookid Jun 27, 2024
ed05d3a
changed username format (now capitalized first letter for words)
Kaz-ookid Jun 27, 2024
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
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)"
matteosz marked this conversation as resolved.
Show resolved Hide resolved
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
Loading