mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-28 02:38:00 +00:00
feat(web): add PEZMessage on-chain E2E encrypted messaging UI
- x25519 ECDH + XChaCha20-Poly1305 encryption via @noble libs - Key derivation from wallet signRaw, private key held in memory only - Messaging pallet integration (registerEncryptionKey, sendMessage, inbox) - Inbox polling every 12s, auto-decrypt when key unlocked - ComposeDialog with recipient key validation and 512-byte limit - Settings moved from grid to nav bar gear icon, PEZMessage takes its slot - i18n translations for all 6 languages (en, tr, kmr, ckb, ar, fa)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import { hexToBytes, bytesToHex } from './crypto';
|
||||
|
||||
export interface EncryptedMessage {
|
||||
sender: string;
|
||||
blockNumber: number;
|
||||
ephemeralPublicKey: Uint8Array;
|
||||
nonce: Uint8Array;
|
||||
ciphertext: Uint8Array;
|
||||
}
|
||||
|
||||
// --- Storage queries ---
|
||||
|
||||
export async function getEncryptionKey(
|
||||
api: ApiPromise,
|
||||
address: string
|
||||
): Promise<Uint8Array | null> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result = await (api.query as any).messaging.encryptionKeys(address);
|
||||
if (result.isNone || result.isEmpty) return null;
|
||||
const hex = result.unwrap().toHex();
|
||||
return hexToBytes(hex);
|
||||
}
|
||||
|
||||
export async function getCurrentEra(api: ApiPromise): Promise<number> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const era = await (api.query as any).messaging.currentEra();
|
||||
return era.toNumber();
|
||||
}
|
||||
|
||||
export async function getInbox(
|
||||
api: ApiPromise,
|
||||
era: number,
|
||||
address: string
|
||||
): Promise<EncryptedMessage[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result = await (api.query as any).messaging.inbox([era, address]);
|
||||
if (result.isEmpty || result.length === 0) return [];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return result.map((msg: Record<string, any>) => ({
|
||||
sender: msg.sender.toString(),
|
||||
blockNumber: msg.blockNumber?.toNumber?.() ?? msg.block_number?.toNumber?.() ?? 0,
|
||||
ephemeralPublicKey: hexToBytes(
|
||||
msg.ephemeralPublicKey?.toHex?.() ?? msg.ephemeral_public_key?.toHex?.() ?? '0x'
|
||||
),
|
||||
nonce: hexToBytes(msg.nonce.toHex()),
|
||||
ciphertext: hexToBytes(msg.ciphertext.toHex()),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getSendCount(
|
||||
api: ApiPromise,
|
||||
era: number,
|
||||
address: string
|
||||
): Promise<number> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const count = await (api.query as any).messaging.sendCount([era, address]);
|
||||
return count.toNumber();
|
||||
}
|
||||
|
||||
// --- TX builders ---
|
||||
|
||||
export function buildRegisterKeyTx(api: ApiPromise, publicKey: Uint8Array) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (api.tx as any).messaging.registerEncryptionKey(bytesToHex(publicKey));
|
||||
}
|
||||
|
||||
export function buildSendMessageTx(
|
||||
api: ApiPromise,
|
||||
recipient: string,
|
||||
ephemeralPubKey: Uint8Array,
|
||||
nonce: Uint8Array,
|
||||
ciphertext: Uint8Array
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (api.tx as any).messaging.sendMessage(
|
||||
recipient,
|
||||
bytesToHex(ephemeralPubKey),
|
||||
bytesToHex(nonce),
|
||||
bytesToHex(ciphertext)
|
||||
);
|
||||
}
|
||||
|
||||
export function buildAcknowledgeTx(api: ApiPromise) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (api.tx as any).messaging.acknowledgeMessages();
|
||||
}
|
||||
Reference in New Issue
Block a user