From 70fca92e781696a057089bc8bc48adebdf6e017e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Tue, 15 Aug 2023 13:48:37 +0200 Subject: [PATCH] [age-withdraw] WiP: first types and adjustments --- packages/taler-util/src/taler-crypto.ts | 25 +----- packages/taler-util/src/taler-types.ts | 83 +++++++++++++++++++ packages/taler-wallet-core/src/db.ts | 14 +++- .../src/operations/withdraw.ts | 7 ++ 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index d52edc1e5..396ac89e1 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -1254,30 +1254,9 @@ export namespace AgeRestriction { age: number, ): Promise { invariant((ageMask & 1) === 1); - const numPubs = countAgeGroups(ageMask) - 1; - const numPrivs = getAgeGroupIndex(ageMask, age); + const seed = getRandomBytes(32); - const pubs: Edx25519PublicKey[] = []; - const privs: Edx25519PrivateKey[] = []; - - for (let i = 0; i < numPubs; i++) { - const priv = await Edx25519.keyCreate(); - const pub = await Edx25519.getPublic(priv); - pubs.push(pub); - if (i < numPrivs) { - privs.push(priv); - } - } - - return { - commitment: { - mask: ageMask, - publicKeys: pubs.map((x) => encodeCrock(x)), - }, - proof: { - privateKeys: privs.map((x) => encodeCrock(x)), - }, - }; + return restrictionCommitSeeded(ageMask, age, seed); } const PublishedAgeRestrictionBaseKey: Edx25519PublicKey = decodeCrock( diff --git a/packages/taler-util/src/taler-types.ts b/packages/taler-util/src/taler-types.ts index 4d4a60d91..178da87da 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -1788,6 +1788,89 @@ export interface ExchangeRefreshRevealRequest { old_age_commitment?: Edx25519PublicKeyEnc[]; } +export interface ExchangeAgeWithdrawRequest { + // Array of n hash codes of denomination public keys to order. + // These denominations MUST support age restriction as defined in the + // output to /keys. + // The sum of all denomination's values and fees MUST be at most the + // balance of the reserve. The balance of the reserve will be + // immediatley reduced by that amount. + denoms_h: HashCodeString[]; + + // n arrays of kappa entries with blinded coin envelopes. Each + // (toplevel) entry represents kappa canditates for a particular + // coin. The exchange will respond with an index gamma, which is + // the index that shall remain undisclosed during the reveal phase. + // The SHA512 hash $ACH over the blinded coin envelopes is the commitment + // that is later used as the key to the reveal-URL. + blinded_coins_evs: CoinEnvelope[][]; + + // The maximum age to commit to. MUST be the same as the maximum + // age value assigned to the reserve, based on its birthday date. + max_age: number; + + // Signature of TALER_AgeWithdrawRequestPS created with + // the reserves's private key + // using purpose TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW. + reserve_sig: EddsaSignatureString; +} + +export interface ExchangeAgeWithdrawResponse { + // index of the commitments that the client doesn't + // have to disclose + noreveal_index: number; + + // Signature of TALER_AgeWithdrawConfirmationPS whereby + // the exchange confirms the noreveal_index. + exchange_sig: EddsaSignatureString; + + // Public EdDSA key of the exchange that was used to + // generate the signature. Should match one of the exchange's signing + // keys from /keys. Again given explicitly as the client might + // otherwise be confused by clock skew as to which signing key was used. + exchange_pub: EddsaPublicKeyString; +} + +export interface ExchangeAgeWithdrawRevealRequest { + // Array of n of (kappa - 1) disclosed coin master secrets, from + // which the coins' private key, blinding, nonce (for Clause-Schnorr) and + // age-restriction is calculated. + // + // Given each coin's private key and age commitment, the exchange will + // calculate each coin's blinded hash value und use all those (disclosed) + // blinded hashes together with the non-disclosed envelopes coin_evs + // during the verification of the original age-withdraw-commitment. + disclosed_coin_secrets: AgeRestrictedCoinSecret[][]; +} + +// The Master key material from which the coins' private key coin_priv, +// blinding beta and nonce nonce (for Clause-Schnorr) itself are +// derived as usually in wallet-core. Given a coin's master key material, +// the age commitment for the coin MUST be derived from this private key as +// follows: +// +// Let m ∈ {1,...,M} be the maximum age group as defined in the reserve +// that the wallet can commit to. +// +// For age group $AG ∈ {1,...m}, set +// seed = HDKF(coin_secret, "age-commitment", $AG) +// p[$AG] = Edx25519_generate_private(seed) +// and calculate the corresponding Edx25519PublicKey as +// q[$AG] = Edx25519_public_from_private(p[$AG]) +// +// For age groups $AG ∈ {m,...,M}, set +// f[$AG] = HDKF(coin_secret, "age-factor", $AG) +// and calculate the corresponding Edx25519PublicKey as +// q[$AG] = Edx25519_derive_public(PublishedAgeRestrictionBaseKey, f[$AG]) +// +// FIXME: shall we add some flavor to this string? +export type AgeRestrictedCoinSecret = string; + +export interface ExchangeAgeWithdrawRevealResponse { + // List of the exchange's blinded RSA or CS signatures on the new coins. + ev_sigs : BlindedDenominationSignature[]; +} + export interface DepositSuccess { // Optional base URL of the exchange for looking up wire transfers // associated with this transaction. If not given, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index c7d0b0bda..72b4fdfcb 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -657,6 +657,7 @@ export interface PlanchetRecord { */ coinIdx: number; + planchetStatus: PlanchetStatus; lastError: TalerErrorDetail | undefined; @@ -671,6 +672,12 @@ export interface PlanchetRecord { coinEvHash: string; + /** + * Index into the kappa-many planchet commitments per coin + * for the age-withdraw operation. + */ + ageWithdrawIdx?: number; + ageCommitmentProof?: AgeCommitmentProof; } @@ -1423,7 +1430,7 @@ export interface KycPendingInfo { } /** * Group of withdrawal operations that need to be executed. - * (Either for a normal withdrawal or from a reward.) + * (Either for a normal {single-|batch-|age-} withdrawal or from a reward.) * * The withdrawal group record is only created after we know * the coin selection we want to withdraw. @@ -2513,6 +2520,11 @@ export const WalletStoresV1 = { "planchets", describeContents({ keyPath: "coinPub" }), { + byGroupAgeCoin: describeIndex("byGroupAgeCoin", [ + "withdrawalGroupId", + "ageWithdrawIdx", + "coinIdx", + ]), byGroupAndIndex: describeIndex("byGroupAndIndex", [ "withdrawalGroupId", "coinIdx", diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index a1cb29e07..a6622d3fe 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -62,6 +62,10 @@ import { ExchangeWithdrawResponse, WithdrawUriInfoResponse, ExchangeBatchWithdrawRequest, + ExchangeAgeWithdrawRequest, + ExchangeAgeWithdrawRevealRequest, + ExchangeAgeWithdrawResponse, + ExchangeAgeWithdrawRevealResponse, TransactionState, TransactionMajorState, TransactionMinorState, @@ -861,6 +865,7 @@ async function processPlanchetExchangeBatchRequest( coinIdx < wgContext.numPlanchets; coinIdx++ ) { + // FIXME[oec]: Add lookup of planchet for age-withdraw here let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -923,6 +928,8 @@ async function processPlanchetExchangeBatchRequest( // FIXME: handle individual error codes better! + // FIXME[oec]: add age-withdraw-request here + if (args.useBatchRequest) { const reqUrl = new URL( `reserves/${withdrawalGroup.reservePub}/batch-withdraw`,