Skip to content

Deterministic Aliases#284

Open
MEZTech-LLC wants to merge 21 commits into
K-Kluster:stagingfrom
MEZTech-LLC:staging
Open

Deterministic Aliases#284
MEZTech-LLC wants to merge 21 commits into
K-Kluster:stagingfrom
MEZTech-LLC:staging

Conversation

@MEZTech-LLC

Copy link
Copy Markdown
Contributor

New implementation (deterministic, but non-unified aliases).
Distilled related documentation.

@HocusLocusTee

HocusLocusTee commented Dec 23, 2025

Copy link
Copy Markdown
Collaborator

Functional test - seems to work as intended.

You'll need to revise the 'Offline Handshake' flow though as it's now broken.
How to access: Long hold on the new conversation + symbol.

Whether we remove this and absorb it as part of the discrete mode - makes sense) - but this hidden mode is far 'cleaner' than the current new chat window (speaking UI here). So maybe some consolidation needs to be done.

Also, adding a discrete chat should probably still show a message or something that handshake is completed? Maybe something like "Discrete Chat Initiated".

Also, I'm still not sold on these docs being part of the repo.

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

^ Edited above.

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

Also FYI, maybe a little more detail in the PR desc next time.

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

**"You'll need to revise the 'Offline Handshake' flow though as it's now broken.
How to access: Long hold on the new conversation + symbol.

Whether we remove this and absorb it as part of the discrete mode - makes sense) - but this hidden mode is far 'cleaner' than the current new chat window (speaking UI here). So maybe some consolidation needs to be done."**

  • Wasn't aware of that flow. Somewhat disagree that the hidden mode is cleaner. It's quite difficult to discover despite being a pretty important feature (imo). What do you recommend in terms of merging the two? My opinion is we should just remove the hidden mode and keep the optional discrete convo checkbox, but open to leaving the "hidden option"/existing offline handshake flow if we can figure out a clean way to make it more discoverable.

"Also, adding a discrete chat should probably still show a message or something that handshake is completed? Maybe something like "Discrete Chat Initiated"."

  • Roger that

"Also, I'm still not sold on these docs being part of the repo."

  • Sure, can remove. I mostly left them in there just to help with debugging as we add this feature.

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

**"You'll need to revise the 'Offline Handshake' flow though as it's now broken.

How to access: Long hold on the new conversation + symbol.

Whether we remove this and absorb it as part of the discrete mode - makes sense) - but this hidden mode is far 'cleaner' than the current new chat window (speaking UI here). So maybe some consolidation needs to be done."**

  • Wasn't aware of that flow. Somewhat disagree that the hidden mode is cleaner. It's quite difficult to discover despite being a pretty important feature (imo). What do you recommend in terms of merging the two? My opinion is we should just remove the hidden mode and keep the optional discrete convo checkbox, but open to leaving the "hidden option"/existing offline handshake flow if we can figure out a clean way to make it more discoverable.

"Also, adding a discrete chat should probably still show a message or something that handshake is completed? Maybe something like "Discrete Chat Initiated"."

  • Roger that

"Also, I'm still not sold on these docs being part of the repo."

  • Sure, can remove. I mostly left them in there just to help with debugging as we add this feature.

I should've specified... I don't think the 'hidden-ness' is cleaner - rather the model itself.

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

Alright,
Perhaps having tabs for each convo type at the top of the modal is better then? (Handshake and Offline-Handshake) where the offline handshake tab shows the existing modal?

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

Alright,

Perhaps having tabs for each convo type at the top of the modal is better then? (Handshake and Offline-Handshake) where the offline handshake tab shows the existing modal?

Decent idea. Maybe let's leave it as is, do the clean up and the review of core idea and after I'm happy to propose some UI work.

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

@HocusLocusTee
Made changes discussed above + added a fix for an edge case handshake logic bug.
If user A and B had an ongoing discrete chat and user A deleted their conversation, if user A attempts to "rehandshake" (non-discrete) with B, user A would not be able to receive messages from B due to no handshake response being received.
Changed behavior to automatically send a handshake response for any chat that is currently "active" in response to a handshake attempt.

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

Part of the discrete conversation flow should give the option to self stash the alias.

Also, self stash or not - from a UX perspective we should add a self (off-chain) message saying a discrete handshake was completed.

This will also add a date time so we don't get a blank card with no reference like the image below:

image

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

Ok added a toast for the discrete message init, but will change it to a message in the thread.

What do you mean by "self stash the alias"?

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

What do you mean by "self stash the alias"?

In the current system we send a 'self stash' tx to ourselves - which is indexed by the indexer - so we can keep the handshake between difference devices.

Now, in the proposed alias changes: if I am A and set up a discrete handshake with B, then chance devices - we will need to self stash this to sync it.

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

@HocusLocusTee
Added proposed changes.

Also something I noticed that would be a useful future improvement; seems like contact names/nicknames are not stored anywhere except the local db. Should probably track these on the indexer using some sort of similar self-stash method.

@HocusLocusTee

Copy link
Copy Markdown
Collaborator
  1. If A has no funds, they cannot start Discrete chat. (They should be able to). We should find a way to segregate the self stash tx and the creation of chat. See old Offline Handshake flow.
image
  1. Still trying to think of a better way to handle new chats. This branch currently adds a 'Discrete Chat Initiated' message but the in chat link for that is now a dead-link. https://explorer-tn10.kaspa.org/txs/discrete_start_b2bcf46f-2df1-46df-b9d7-c04e317411f6. We could do a hacky patch but there might be a better option?

RE: your statement: Sure, seems low priority. But IMO it's outside of this scope. Maybe open an issue ticket for it.

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

"If A has no funds, they cannot start Discrete chat. (They should be able to). We should find a way to segregate the self stash tx and the creation of chat. See old Offline Handshake flow."

Not sure I understand this.
Doesn't self-stashing require the user to have at least some funds, otherwise they wouldn't be able to conduct a tx (cover fees).
Do you just mean that the 0.2 KAS balance shouldn't be required? Or do you mean that the .2 KAS balance shouldn't be required AND if the balance is 0 (not enough to cover the network fee) the self-stash should just be skipped?

@HocusLocusTee

Copy link
Copy Markdown
Collaborator

Self stash does require KAS. But setting up a discrete handshake does not.

Right now, no balance blocks me from even creating the handshake.

If balance is 0, we could automatically skip the self stash... but we should eventually add a way to stash it again at a later date.

… handshaking an emepty wallet (now always listening when initiatedByMe is true
@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

Got it - self stash is now skipped when funds are not available.

Comment thread src/store/messaging.store.ts Outdated
});
// Optionally create self-stash for cross-device sync (requires minimal funds for network fees)
// Check if user has sufficient balance before attempting
const minFeeAmount = BigInt(100000); // ~0.001 KAS for network fees

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use const minAmount = kaspaToSompi("0.19"); in other places. maybe we should generalize this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as is for now - linter flags usage in the subsequent conditional if I change it to use kaspaToSompi since undefined can be returned.

import { ImagePresenter } from "../Modals/ImagePresenter";
import { BroadcastParticipantInfo } from "../Modals/BroadcastParticipantInfo";
import { QrScannerModal } from "../Modals/QrScannerModal";
import { OffChainHandshakeModal } from "../Modals/OffChainHandshakeModal";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to clean up OffChainHandshakeModal.

}) => {
const handleShortPress = () => {
onNewChat();
const handleClick = () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this component at all anymore. It can be styled with a regular + button and none of the 'holdable' complexity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had left it for future usecases, but will remove

Comment thread DEBUGGING_ALIAS_ROUTING.md Outdated
@@ -0,0 +1,306 @@
# Debugging Alias Routing - Logging Added

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still dont think we need this doc. There are plenty of decent comments explaining how this works in the new util file.

}
} else {
console.log(
"[createDiscreteConversation] Skipping self-stash - insufficient balance for network fees (conversation works locally)"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. But how does one self stash at a later date?
We should add a warn that we did not self stash due to no balance.
We also need to add the ability to self stash at a later date (and track this somehow). I don't think it should be part of this PR as it'll muddle the context - but we should be aware and open an issue / put it into future work.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want me to open this as a new issue? Or can you?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/store/messaging.store.ts Outdated
@HocusLocusTee

Copy link
Copy Markdown
Collaborator

@MEZTech-LLC please see the commits in this branch:
#286

Feel free to cherry-pick them if you approve.

@MEZTech-LLC

Copy link
Copy Markdown
Contributor Author

Looks good to me

Comment thread src/store/messaging.store.ts Outdated
shouldSendResponse: boolean;
conversationId: string;
};
if (typedResult.shouldSendResponse) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should extract some of this to the conversation manager stop. Maybe something like sendAutomaticHandshakeResponse

Comment thread src/store/messaging.store.ts Outdated
// add an offline handshake

/**
* @deprecated Manual alias exchange is no longer needed. Aliases are derived deterministically.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably dont need legacy comments like this and the one on line 1247

Comment thread src/service/block-processor-service.ts Outdated
this.monitoredAddresses.clear();
const conversations = conversationManager.getMonitoredConversations();

console.log("[block-processor] Updating monitored aliases:", {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove the logs you've added. They are way too noisy.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MEZTech-LLC I've done it for you. feel free to just pick the last 2 commits so I don't claim write ownership over your other commits
#289

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @HocusLocusTee - Apologies I couldn't get to this sooner; other pressing tasks came up

@IzioDev IzioDev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rust part sounds good, I'll dedicate more time on the application code later (and actually try it locally too :) ).

Comment thread cipher/src/lib.rs Outdated
Comment on lines +351 to +353
// Get my public key from my private key
let my_public_key = my_private_key.to_public_key()
.map_err(|_| JsError::new("Failed to derive public key"))?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: We can avoid computing pk from sk if the client provide a public key instead of the private key.
Public key can safely be computed once by the client upon startup (where we initiate the wallet state), and provide it here.
same for xonly_pk (below)

Comment thread cipher/src/lib.rs Outdated
Comment on lines +375 to +381
// Parse their address to extract their public key
let address = Address::try_from(their_address)
.map_err(|e| JsError::new(&format!("Invalid address: {}", e)))?;

// Extract X-only public key from address (32 bytes, no parity)
let their_xonly_pk = XOnlyPublicKey::from_slice(address.payload.as_slice())
.map_err(|e| JsError::new(&format!("Invalid public key in address: {}", e)))?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is done twice, once here and once in derive_alias_with_context.

I propose to let responsibility to calling site to compute address + xonly_pk and now derive_alias_with_context takes a xonly_pk as argument

Comment thread cipher/src/lib.rs Outdated

// Construct info string: "chat" || shared_secret || context_pubkey
let mut info = Vec::new();
info.extend_from_slice(b"chat");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we re-use another domain separation, maybe we should name chat more explicitly.

What it is, is: a 1:1 conversation alias for now. I propose to simplify as alias as it may be a general concept within the application for other purpose as-well

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or dm_alias, or pchat (private chat) or w/e

nit: could be made under an enum: enum HashDomain { ... }

# Conflicts:
#	src/components/Modals/NewChatForm.tsx
#	src/service/block-processor-service.ts
#	src/service/conversation-manager-service.ts
#	src/store/ui.store.ts
// In deterministic system: I monitor myAlias, they send to theirAlias
// Due to ECDH symmetry: their theirAlias === my myAlias
monitored.push({
alias: conversationAndContact.conversation.myAlias,

@HocusLocusTee HocusLocusTee Feb 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct, but will be an issue with old v1 alias. Do we just optimistically hope they upgrade? If so, why do we then even give them the option to upgrade if the code isn't fully compatible with v1.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, maybe just lock the conv and force manual upgrade (no hidden / automatic mechanism), wdyt?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it with just running a db migration on boot?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, on boot it adds a version field at conversation level (the entity holding alias data) and we set it to v1 by default.
Later, when you enter a conversation, if it's v1, you're asked to migrate (it replays handshake, and set to v2).
All new conversation are created as v2

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack on that. But why not just compute new alias on db bump / opening new version?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely right, 0 need into re-initiating an handshake. Besides it would reveal stealth conversation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be good with latest commit, let me know what you think, silently migrate basically

<Button
variant="primary"
onClick={sendUpgradeResponse}
disabled={isSubmitting}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should disable this with a funds check and a message if they try to send with no funds?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General Q for this great feature: Wonder if we need a way to 'force' an upgrade. Lets say they are not getting upgrade messages, we can compute this without interaction, so should we allow it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed because I don't think it has a use-case at the moment, maybe for dcom upgrade (not even sure that'll be necessary)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants