diff options
-rw-r--r-- | packages/taler-util/src/taler-crypto.ts | 25 | ||||
-rw-r--r-- | packages/taler-util/src/taler-types.ts | 83 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 15 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 5 |
4 files changed, 104 insertions, 24 deletions
diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts index 3ad388794..de5be71a1 100644 --- a/packages/taler-util/src/taler-crypto.ts +++ b/packages/taler-util/src/taler-crypto.ts @@ -1253,30 +1253,9 @@ export namespace AgeRestriction { age: number, ): Promise<AgeCommitmentProof> { invariant((ageMask & 1) === 1); - const numPubs = countAgeGroups(ageMask) - 1; - const numPrivs = getAgeGroupIndex(ageMask, age); - - 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); - } - } + const seed = getRandomBytes(32); - 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 d8fdc2519..ebb0291f5 100644 --- a/packages/taler-util/src/taler-types.ts +++ b/packages/taler-util/src/taler-types.ts @@ -1889,6 +1889,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[]; +} + interface DepositConfirmationSignature { // The EdDSA signature of `TALER_DepositConfirmationPS` using a current // `signing key of the exchange <sign-key-priv>` affirming the successful diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index d59085dcc..46a073156 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -707,6 +707,7 @@ export interface PlanchetRecord { */ coinIdx: number; + planchetStatus: PlanchetStatus; lastError: TalerErrorDetail | undefined; @@ -721,6 +722,12 @@ export interface PlanchetRecord { coinEvHash: string; + /** + * Index into the kappa-many planchet commitments per coin + * for the age-withdraw operation. + */ + ageWithdrawIdx?: number; + ageCommitmentProof?: AgeCommitmentProof; } @@ -1412,7 +1419,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. @@ -2440,6 +2447,12 @@ export const WalletStoresV1 = { "planchets", describeContents<PlanchetRecord>({ keyPath: "coinPub" }), { + byGroupAgeCoin: describeIndex( + "byGroupAgeCoin", [ "withdrawalGroupId", "ageWithdrawIdx", "coinIdx" ], + { + unique: true, + }, + ), 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 2c9c95d4c..eff427bec 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, @@ -865,6 +869,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, |