taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit cf65329a025237308e6a28cd321bac7f05769a22
parent afb8d939ff578f354c46a26cc6c44392bc27ef8f
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Tue, 11 Nov 2025 16:26:10 +0100

update to revamped mailbox API

Diffstat:
Mpackages/taler-util/src/http-client/mailbox.ts | 47+++++++++++++++++++++++++----------------------
Mpackages/taler-util/src/types-taler-mailbox.ts | 54+++++++++++++++++++++++-------------------------------
Mpackages/taler-wallet-core/src/mailbox.ts | 33+++++++++++++++------------------
3 files changed, 63 insertions(+), 71 deletions(-)

diff --git a/packages/taler-util/src/http-client/mailbox.ts b/packages/taler-util/src/http-client/mailbox.ts @@ -23,7 +23,6 @@ import { OperationOk, ResultByMethod, TalerMailboxApi, - MailboxMessageDeletionRequest, carefullyParseConfig, codecForTalerMailboxConfigResponse, codecForTalerMailboxRateLimitedResponse, @@ -32,18 +31,16 @@ import { opKnownAlternativeHttpFailure, opKnownHttpFailure, opUnknownHttpFailure, - TalerMailboxConfigResponse, - MailboxMessageKeys, - codecForTalerMailboxMessageKeys, + MailboxMetadata as MailboxMetadata, + codecForTalerMailboxMetadata, opSuccessFromHttp, - EmptyObject, - MailboxMessageKeysUpdateRequest, + MailboxRegisterRequest, encodeCrock, - sha512, decodeCrock, codecForEmptyObject, MailboxConfiguration, eddsaGetPublic, + EddsaSignatureString, } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, @@ -203,21 +200,23 @@ export class TalerMailboxInstanceHttpClient { async deleteMessages(args: { mailboxConf: MailboxConfiguration; matchIf: string; - body: MailboxMessageDeletionRequest; + count: number; + signature: EddsaSignatureString; }): Promise< | OperationOk<void> | OperationFail<HttpStatusCode.Forbidden> | OperationFail<HttpStatusCode.NotFound> >{ - const { mailboxConf, matchIf: etag, body } = args; + const { mailboxConf, matchIf: etag, count: count, signature: signature } = args; const mailboxPubkeyString = encodeCrock(eddsaGetPublic(decodeCrock(mailboxConf.privateKey))); - const url = new URL(`private/${mailboxPubkeyString.toUpperCase()}`, this.baseUrl); + const url = new URL(`${mailboxPubkeyString.toUpperCase()}?count=${count}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { - method: "POST", - body: body, - headers: { "If-Match" : etag }, + method: "DELETE", + headers: { + "If-Match" : etag, + "Taler-Mailbox-Delete-Signature" : signature}, cancellationToken: this.cancellationToken, }); @@ -234,14 +233,14 @@ export class TalerMailboxInstanceHttpClient { } /** - * https://docs.taler.net/core/api-mailbox.html#key-directory + * https://docs.taler.net/core/api-mailbox.html#get--info-$H_MAILBOX */ - async getKeys(hMailbox: string) : Promise< - | OperationOk<MailboxMessageKeys> + async getMailboxInfo(hMailbox: string) : Promise< + | OperationOk<MailboxMetadata> | OperationFail<HttpStatusCode.NotFound> | OperationFail<HttpStatusCode.TooManyRequests> >{ - const url = new URL(`keys/${hMailbox.toUpperCase()}`, this.baseUrl); + const url = new URL(`info/${hMailbox.toUpperCase()}`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "GET", @@ -251,7 +250,7 @@ export class TalerMailboxInstanceHttpClient { switch (resp.status) { case HttpStatusCode.Ok: { return opSuccessFromHttp(resp, - codecForTalerMailboxMessageKeys()); + codecForTalerMailboxMetadata()); } case HttpStatusCode.NotFound: { return opKnownAlternativeHttpFailure(resp, resp.status, codecForEmptyObject()); @@ -259,19 +258,20 @@ export class TalerMailboxInstanceHttpClient { case HttpStatusCode.TooManyRequests: { return opKnownAlternativeHttpFailure(resp, resp.status, codecForTalerMailboxRateLimitedResponse()); } - default: + default: return opUnknownHttpFailure(resp); } } /** - * https://docs.taler.net/core/api-mailbox.html#post--keys + * https://docs.taler.net/core/api-mailbox.html#post--register */ - async updateKeys(req: MailboxMessageKeysUpdateRequest) : Promise< + async updateRegistration(req: MailboxRegisterRequest) : Promise< | OperationOk<void> | OperationFail<HttpStatusCode.Forbidden> + | OperationFail<HttpStatusCode.PaymentRequired> >{ - const url = new URL(`keys`, this.baseUrl); + const url = new URL(`register`, this.baseUrl); const resp = await this.httpLib.fetch(url.href, { method: "POST", @@ -286,6 +286,9 @@ export class TalerMailboxInstanceHttpClient { case HttpStatusCode.Forbidden: { return opKnownHttpFailure(resp.status, resp); } + case HttpStatusCode.PaymentRequired: { + return opKnownHttpFailure(resp.status, resp); + } default: return opUnknownHttpFailure(resp); } diff --git a/packages/taler-util/src/types-taler-mailbox.ts b/packages/taler-util/src/types-taler-mailbox.ts @@ -46,7 +46,8 @@ export const codecForTalerMailboxConfigResponse = buildCodecForObject<TalerMailboxConfigResponse>() .property("version", codecForString()) .property("name", codecForConstString("taler-mailbox")) - .property("message_fee", codecForAmountString()) + .property("monthly_fee", codecForAmountString()) + .property("registration_update_fee", codecForAmountString()) .property("message_body_bytes", codecForNumber()) .property("message_response_limit", codecForNumber()) .property("delivery_period", codecForDuration) @@ -62,9 +63,6 @@ export interface TalerMailboxConfigResponse { // Name of the protocol. name: "taler-mailbox"; - // Fee per message. - message_fee: AmountString; - // Fixed size of message body. message_body_bytes: Integer; @@ -75,20 +73,30 @@ export interface TalerMailboxConfigResponse { // How long will the service store a message // before giving up on delivery? delivery_period: RelativeTime; + + // How much is the cost of a single + // registration (update) of a mailbox + // May be 0 for a free update/registration. + registration_update_fee: AmountString; + + // How much is the cost of a single + // registration period (30 days) of a mailbox + // May be 0 for a free registration. + monthly_fee: AmountString; } export const codecForTalerMailboxMessageKeysUpdateRequest = - (): Codec<MailboxMessageKeysUpdateRequest> => -buildCodecForObject<MailboxMessageKeysUpdateRequest>() - .property("keys", codecForTalerMailboxMessageKeys()) + (): Codec<MailboxRegisterRequest> => +buildCodecForObject<MailboxRegisterRequest>() + .property("mailbox_metadata", codecForTalerMailboxMetadata()) .property("signature", codecForString()) - .build("TalerMailboxApi.MailboxMessageKeysUpdateRequest"); + .build("TalerMailboxApi.MailboxRegisterRequest"); -export interface MailboxMessageKeysUpdateRequest { +export interface MailboxRegisterRequest { - // Keys to add/update for a mailbox. - keys: MailboxMessageKeys; + // Mailbox metadata. + mailbox_metadata: MailboxMetadata; // Signature by the mailbox's signing key affirming // the update of keys, of purpuse @@ -99,9 +107,9 @@ export interface MailboxMessageKeysUpdateRequest { } -export const codecForTalerMailboxMessageKeys = - (): Codec<MailboxMessageKeys> => -buildCodecForObject<MailboxMessageKeys>() +export const codecForTalerMailboxMetadata = + (): Codec<MailboxMetadata> => +buildCodecForObject<MailboxMetadata>() .property("signingKey", codecForEddsaPublicKey()) .property("signingKeyType", codecForConstString("EdDSA")) .property("encryptionKey", codecForString()) @@ -110,7 +118,7 @@ buildCodecForObject<MailboxMessageKeys>() .build("TalerMailboxApi.MailboxMessageKeys"); -export interface MailboxMessageKeys { +export interface MailboxMetadata { // The mailbox signing key. // Note that $H_MAILBOX == H(singingKey). @@ -160,19 +168,3 @@ export interface MailboxRateLimitedResponse { hint: string; } - -export interface MailboxMessageDeletionRequest { - - // Number of messages to delete. (Starting from the beginning - // of the latest GET response). - count: Integer; - - // SHA-512 hash over all messages to delete. - checksum: HashCodeString; - - // Signature by the mailbox's private key affirming - // the deletion of the messages, of purpuse - // TALER_SIGNATURE_WALLET_MAILBOX_DELETE_MESSAGES. - signature: EddsaSignatureString; - -} diff --git a/packages/taler-wallet-core/src/mailbox.ts b/packages/taler-wallet-core/src/mailbox.ts @@ -43,12 +43,11 @@ import { SendTalerUriMailboxMessageRequest, hpkeSealOneshot, hpkeSecretKeyGetPublic, - MailboxMessageKeys, - MailboxMessageKeysUpdateRequest, + MailboxMetadata, + MailboxRegisterRequest, eddsaSign, Duration, AbsoluteTime, - eddsaVerify, TalerSignaturePurpose, } from "@gnu-taler/taler-util"; import { @@ -157,20 +156,20 @@ export async function updateMailboxKeys( vMsg.setUint32(4, TalerSignaturePurpose.MAILBOX_KEYS_UPDATE); const msgToSign = new Uint8Array([...(new Uint8Array(messageHeader)), ...digest]); const signature = eddsaSign(msgToSign, privateSigningKey); - const keys: MailboxMessageKeys = { + const info: MailboxMetadata = { signingKey: encodeCrock(signingKey), signingKeyType: "EdDSA", // FIXME only supported key type ATM, encryptionKey: encodeCrock(encryptionKey), encryptionKeyType: "X25519", // FIXME only supported encryption key type ATM expiration: mailboxConf.expiration, }; - const req: MailboxMessageKeysUpdateRequest = { - keys: keys, + const req: MailboxRegisterRequest = { + mailbox_metadata: info, signature: encodeCrock(signature), }; const mailboxClient = new TalerMailboxInstanceHttpClient(mailboxConf.mailboxBaseUrl, wex.http); - succeedOrThrow(await mailboxClient.updateKeys(req)); + succeedOrThrow(await mailboxClient.updateRegistration(req)); return; } @@ -303,22 +302,20 @@ export async function refreshMailbox( } // Message header: 8 byte + 64 byte SHA512 digest to sign // We hash all messages - const messageHeader = new ArrayBuffer(8); - const vMsg = new DataView(messageHeader); - const digest = sha512(new Uint8Array(messages)); - vMsg.setUint32(0, 8 + 64); + const messageBuffer = new ArrayBuffer(16); + const vMsg = new DataView(messageBuffer); + vMsg.setUint32(0, 4 * 4); vMsg.setUint32(4, TalerSignaturePurpose.MAILBOX_MESSAGES_DELETE); - const msgToSign: Uint8Array = new Uint8Array([...(new Uint8Array(messageHeader)), ...digest]); + vMsg.setUint32(8, parseInt(res.body.etag)); + vMsg.setUint32(12, numMessages); + const msgToSign: Uint8Array = new Uint8Array(messageBuffer); const privateSigningKey: Uint8Array = decodeCrock(mailboxConf.privateKey); const signature = eddsaSign(msgToSign, privateSigningKey); succeedOrThrow(await mailboxClient.deleteMessages({ mailboxConf: mailboxConf, matchIf: res.body.etag, - body: { - count: numMessages, - checksum: encodeCrock(digest), - signature: encodeCrock(signature), - } + count: numMessages, + signature: encodeCrock(signature), })); return records; } @@ -364,7 +361,7 @@ export async function sendTalerUriMessage( default: return {}; } - const resKeys = await mailboxClient.getKeys(req.contact.mailboxAddress); + const resKeys = await mailboxClient.getMailboxInfo(req.contact.mailboxAddress); var keys; switch (resKeys.case) { case "ok":