commit 35047ee77b6c9dbc82f3815e716ea5e4688a6ad0
parent 0997ade82af82e625fb0b4da40f2543bac38992d
Author: Antoine A <>
Date: Tue, 30 Jun 2026 12:51:07 +0200
prepared-transfer-api: support new signatures
Diffstat:
7 files changed, 129 insertions(+), 29 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-prepared-transfer.ts b/packages/taler-harness/src/integrationtests/test-prepared-transfer.ts
@@ -19,11 +19,11 @@
*/
import {
alternativeOrThrow,
- encodeCrock,
succeedOrThrow,
TalerCoreBankHttpClient,
TalerErrorCode,
TalerPreparedTransferHttpClient,
+ TalerProtocolTimestamp,
TalerWireGatewayHttpClient,
} from "@gnu-taler/taler-util";
import { createSyncCryptoApi } from "@gnu-taler/taler-wallet-core";
@@ -86,6 +86,15 @@ export async function runPreparedTransferTest(t: GlobalTestState) {
// Check registration
const auth_keypair = await cryptoApi.createEddsaKeypair({});
const reserve_keypair = await cryptoApi.createEddsaKeypair({});
+ const sigRes = await cryptoApi.signPreparedTransferRegister({
+ credit_account: exchangePaytoUri,
+ credit_amount: "TESTKUDOS:10",
+ type: "reserve",
+ alg: "EdDSA",
+ account_pub: reserve_keypair.pub,
+ authorization_priv: auth_keypair.priv,
+ recurrent: false,
+ });
succeedOrThrow(
await preparedClient.register({
credit_account: exchangePaytoUri,
@@ -94,29 +103,22 @@ export async function runPreparedTransferTest(t: GlobalTestState) {
alg: "EdDSA",
account_pub: reserve_keypair.pub,
authorization_pub: auth_keypair.pub,
- authorization_sig: (
- await cryptoApi.eddsaSign({
- msg: reserve_keypair.pub,
- priv: auth_keypair.priv,
- })
- ).sig,
+ authorization_sig: sigRes.sig,
recurrent: false,
}),
);
// Check unregistration
- const timestamp = new Date().toISOString();
- const uint8 = new TextEncoder().encode(timestamp);
+ const timestamp = TalerProtocolTimestamp.now();
+ const sigUnres = await cryptoApi.signPreparedTransferUnregister({
+ timestamp,
+ authorization_priv: auth_keypair.priv,
+ });
succeedOrThrow(
await preparedClient.unregister({
- timestamp: timestamp,
+ timestamp,
authorization_pub: auth_keypair.pub,
- authorization_sig: (
- await cryptoApi.eddsaSign({
- msg: encodeCrock(uint8),
- priv: auth_keypair.priv,
- })
- ).sig,
+ authorization_sig: sigUnres.sig,
}),
);
@@ -140,12 +142,7 @@ export async function runPreparedTransferTest(t: GlobalTestState) {
alg: "EdDSA",
account_pub: reserve_keypair.pub,
authorization_pub: auth_keypair.pub,
- authorization_sig: (
- await cryptoApi.eddsaSign({
- msg: reserve_keypair.pub,
- priv: auth_keypair.priv,
- })
- ).sig,
+ authorization_sig: sigRes.sig,
recurrent: false,
}),
);
diff --git a/packages/taler-util/src/http-client/bank-prepared.ts b/packages/taler-util/src/http-client/bank-prepared.ts
@@ -103,10 +103,10 @@ export class TalerPreparedTransferHttpClient {
method: "POST",
body,
});
- console.log(body);
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForRegistrationResponse());
+ case HttpStatusCode.Forbidden:
case HttpStatusCode.BadRequest:
case HttpStatusCode.Unauthorized:
case HttpStatusCode.NotFound:
@@ -133,13 +133,13 @@ export class TalerPreparedTransferHttpClient {
case HttpStatusCode.BadRequest:
case HttpStatusCode.Unauthorized:
case HttpStatusCode.NotFound:
+ case HttpStatusCode.Forbidden:
return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.Conflict: {
const body = await readTalerErrorResponse(resp);
const details = codecForTalerErrorDetail().decode(body);
switch (details.code) {
case TalerErrorCode.BANK_OLD_TIMESTAMP:
- case TalerErrorCode.BANK_BAD_SIGNATURE:
return opKnownTalerFailure(resp, details.code, details);
default:
return opKnownHttpFailure(resp.status, resp, details);
diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts
@@ -1025,6 +1025,14 @@ export interface FreshCoin {
ageCommitmentProof: AgeCommitmentProof | undefined;
}
+export function bufferForUint16(n: number): Uint8Array {
+ const arrBuf = new ArrayBuffer(2);
+ const buf = new Uint8Array(arrBuf);
+ const dv = new DataView(arrBuf);
+ dv.setUint16(0, n);
+ return buf;
+}
+
export function bufferForUint32(n: number): Uint8Array {
const arrBuf = new ArrayBuffer(4);
const buf = new Uint8Array(arrBuf);
@@ -1142,6 +1150,7 @@ export class SignaturePurposeBuilder {
const dvbuf = new DataView(buf);
dvbuf.setUint32(0, payloadLen + 4 + 4);
dvbuf.setUint32(4, this.purposeNum);
+ console.log((u8buf))
return u8buf;
}
}
diff --git a/packages/taler-util/src/taler_signatures.ts b/packages/taler-util/src/taler_signatures.ts
@@ -401,6 +401,11 @@ export enum TalerSignaturePurpose {
*/
WALLET_TOKEN_USE = 1222,
+
+ WALLET_PREPARED_TRANSFER_REGISTER = 1224,
+
+ WALLET_PREPARED_TRANSFER_UNREGISTER = 1225,
+
/**
* Signature on a denomination key announcement.
diff --git a/packages/taler-util/src/types-taler-prepared-transfer.ts b/packages/taler-util/src/types-taler-prepared-transfer.ts
@@ -30,7 +30,7 @@ import {
codecOptional,
} from "./codec.js";
import { TalerPreparedTransferApi } from "./index.js";
-import { codecForTimestamp } from "./time.js";
+import { codecForTimestamp, TalerProtocolTimestamp } from "./time.js";
import {
AmountString,
EddsaPublicKey,
@@ -134,8 +134,8 @@ export interface RegistrationResponse {
}
export interface Unregistration {
- // Current timestamp in the ISO 8601
- timestamp: string;
+ // Current timestamp
+ timestamp: TalerProtocolTimestamp;
// Public key used for registration
authorization_pub: EddsaPublicKey;
@@ -225,7 +225,7 @@ export const codecForRegistrationResponse =
export const codecForUnregistration =
(): Codec<TalerPreparedTransferApi.Unregistration> =>
buildCodecForObject<TalerPreparedTransferApi.Unregistration>()
- .property("timestamp", codecForString())
+ .property("timestamp", codecForTimestamp)
.property("authorization_pub", codecForEddsaPublicKey())
.property("authorization_sig", codecForEddsaSignature())
.build("TalerPreparedTransferApi.Unregistration");
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -31,6 +31,7 @@ import {
Amounts,
AmountString,
BlindedDenominationSignature,
+ bufferForUint16,
bufferForUint32,
bufferForUint64,
bufferFromAmount,
@@ -119,6 +120,10 @@ import {
SignContractTermsHashResponse,
SignDeletePurseRequest,
SignDeletePurseResponse,
+ SignPreparedTransferRegisterRequest,
+ SignPreparedTransferRegisterResponse,
+ SignPreparedTransferUnregisterRequest,
+ SignPreparedTransferUnregisterResponse,
SignPurseMergeRequest,
SignPurseMergeResponse,
SignRefundRequest,
@@ -310,6 +315,14 @@ export interface TalerCryptoInterface {
signContractTermsHash(
req: SignContractTermsHashRequest,
): Promise<SignContractTermsHashResponse>;
+
+ signPreparedTransferRegister(
+ req: SignPreparedTransferRegisterRequest,
+ ): Promise<SignPreparedTransferRegisterResponse>;
+
+ signPreparedTransferUnregister(
+ req: SignPreparedTransferUnregisterRequest,
+ ): Promise<SignPreparedTransferUnregisterResponse>;
}
/**
@@ -549,6 +562,16 @@ export const nullCrypto: TalerCryptoInterface = {
): Promise<ValidationResult> {
throw new Error("Function not implemented.");
},
+ signPreparedTransferRegister: function (
+ req: SignPreparedTransferRegisterRequest,
+ ): Promise<SignPreparedTransferRegisterResponse> {
+ throw new Error("Function not implemented.");
+ },
+ signPreparedTransferUnregister: function (
+ req: SignPreparedTransferUnregisterRequest,
+ ): Promise<SignPreparedTransferUnregisterResponse> {
+ throw new Error("Function not implemented.");
+ },
};
export type WithArg<X> = X extends (req: infer T) => infer R
@@ -2330,7 +2353,50 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
sig: encodeCrock(sig),
};
},
-};
+ async signPreparedTransferRegister(
+ tci: TalerCryptoInterfaceR,
+ req: SignPreparedTransferRegisterRequest,
+ ): Promise<SignPreparedTransferRegisterResponse> {
+
+ let b = buildSigPS(
+ TalerSignaturePurpose.WALLET_PREPARED_TRANSFER_REGISTER
+ )
+ .put(hashTruncate32(stringToBytes(req.credit_account + "\0")))
+ .put(bufferFromAmount(req.credit_amount));
+ if (req.type == "reserve") {
+ b = b.put(bufferForUint32(1));
+ } else {
+ b = b.put(bufferForUint32(2));
+ }
+
+ if (req.recurrent) {
+ b = b.put(bufferForUint16(2));
+ } else {
+ b = b.put(bufferForUint16(1));
+ }
+
+ b = b.put(bufferForUint16(1)).put(decodeCrock(req.account_pub));
+
+ return await tci.eddsaSign(tci, {
+ msg: encodeCrock(b.build()),
+ priv: req.authorization_priv,
+ })
+ },
+ async signPreparedTransferUnregister(
+ tci: TalerCryptoInterfaceR,
+ req: SignPreparedTransferUnregisterRequest,
+ ): Promise<SignPreparedTransferUnregisterResponse> {
+
+ const b = buildSigPS(
+ TalerSignaturePurpose.WALLET_PREPARED_TRANSFER_UNREGISTER
+ )
+ .put(timestampRoundedToBuffer(req.timestamp)).build();
+ return await tci.eddsaSign(tci, {
+ msg: encodeCrock(b),
+ priv: req.authorization_priv,
+ })
+ }
+}
export interface EddsaSignRequest {
msg: string;
diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
@@ -387,3 +387,26 @@ export interface SignReservePurseCreateResponse {
purseSig: string;
}
+
+export interface SignPreparedTransferRegisterRequest {
+ credit_account: string;
+ credit_amount: AmountString,
+ type: 'reserve' | 'kyc',
+ recurrent: boolean,
+ alg: 'EdDSA'
+ account_pub: string
+ authorization_priv: string;
+}
+
+export interface SignPreparedTransferRegisterResponse {
+ sig: string;
+}
+
+export interface SignPreparedTransferUnregisterRequest {
+ timestamp: TalerProtocolTimestamp;
+ authorization_priv: string;
+}
+
+export interface SignPreparedTransferUnregisterResponse {
+ sig: string;
+}