gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated (9ba0e859 -> 9e379ef3)


From: gnunet
Subject: [taler-wallet-core] branch master updated (9ba0e859 -> 9e379ef3)
Date: Thu, 04 Nov 2021 20:20:14 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository wallet-core.

    from 9ba0e859 error notifications
     new 11e8060a anastasis-core: fix upload fee computation, prepare for 
payments
     new b94dc8f9 anastasis-core: factor out recovery data computation into 
separate step
     new 9e379ef3 anastasis-core: truth payments, status codes

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/anastasis-core/src/index.ts         | 257 ++++++++++++++++-----------
 packages/anastasis-core/src/reducer-types.ts |  38 ++++
 2 files changed, 193 insertions(+), 102 deletions(-)

diff --git a/packages/anastasis-core/src/index.ts 
b/packages/anastasis-core/src/index.ts
index 2ed69b55..238b658a 100644
--- a/packages/anastasis-core/src/index.ts
+++ b/packages/anastasis-core/src/index.ts
@@ -51,21 +51,17 @@ import {
   codecForActionArgsUpdateExpiration,
   ContinentInfo,
   CountryInfo,
-  MethodSpec,
-  Policy,
-  PolicyProvider,
   RecoveryInformation,
   RecoveryInternalData,
   RecoveryStates,
   ReducerState,
   ReducerStateBackup,
-  ReducerStateBackupUserAttributesCollecting,
   ReducerStateError,
   ReducerStateRecovery,
   SuccessDetails,
-  UserAttributeSpec,
   codecForActionArgsChangeVersion,
   ActionArgsChangeVersion,
+  TruthMetaData,
 } from "./reducer-types.js";
 import fetchPonyfill from "fetch-ponyfill";
 import {
@@ -302,35 +298,6 @@ async function backupEnterUserAttributes(
   return newState;
 }
 
-/**
- * Truth data as stored in the reducer.
- */
-interface TruthMetaData {
-  uuid: string;
-
-  key_share: string;
-
-  policy_index: number;
-
-  pol_method_index: number;
-
-  /**
-   * Nonce used for encrypting the truth.
-   */
-  nonce: string;
-
-  /**
-   * Key that the truth (i.e. secret question answer, email address, mobile 
number, ...)
-   * is encrypted with when stored at the provider.
-   */
-  truth_key: string;
-
-  /**
-   * Truth-specific salt.
-   */
-  truth_salt: string;
-}
-
 async function getTruthValue(
   authMethod: AuthMethod,
   truthUuid: string,
@@ -376,14 +343,19 @@ async function uncompressRecoveryDoc(zippedRd: 
Uint8Array): Promise<any> {
   return JSON.parse(bytesToString(res));
 }
 
-async function uploadSecret(
+/**
+ * Prepare the recovery document and truth metadata based
+ * on the selected policies.
+ */
+async function prepareRecoveryData(
   state: ReducerStateBackup,
-): Promise<ReducerStateBackup | ReducerStateError> {
+): Promise<ReducerStateBackup> {
   const policies = state.policies!;
   const secretName = state.secret_name!;
   const coreSecret: OpaqueData = encodeCrock(
     stringToBytes(JSON.stringify(state.core_secret!)),
   );
+
   // Truth key is `${methodIndex}/${providerUrl}`
   const truthMetadataMap: Record<string, TruthMetaData> = {};
 
@@ -424,6 +396,53 @@ async function uploadSecret(
 
   const csr = await coreSecretEncrypt(policyKeys, coreSecret);
 
+  const escrowMethods: EscrowMethod[] = [];
+
+  for (const truthKey of Object.keys(truthMetadataMap)) {
+    const tm = truthMetadataMap[truthKey];
+    const pol = state.policies![tm.policy_index];
+    const meth = pol.methods[tm.pol_method_index];
+    const authMethod =
+      state.authentication_methods![meth.authentication_method];
+    const provider = state.authentication_providers![
+      meth.provider
+    ] as AuthenticationProviderStatusOk;
+    escrowMethods.push({
+      escrow_type: authMethod.type as any,
+      instructions: authMethod.instructions,
+      provider_salt: provider.salt,
+      truth_salt: tm.truth_salt,
+      truth_key: tm.truth_key,
+      url: meth.provider,
+      uuid: tm.uuid,
+    });
+  }
+
+  const rd: RecoveryDocument = {
+    secret_name: secretName,
+    encrypted_core_secret: csr.encCoreSecret,
+    escrow_methods: escrowMethods,
+    policies: policies.map((x, i) => {
+      return {
+        master_key: csr.encMasterKeys[i],
+        uuids: policyUuids[i],
+        salt: policySalts[i],
+      };
+    }),
+  };
+
+  return {
+    ...state,
+    recovery_data: {
+      recovery_document: rd,
+      truth_metadata: truthMetadataMap,
+    },
+  };
+}
+
+async function uploadSecret(
+  state: ReducerStateBackup,
+): Promise<ReducerStateBackup | ReducerStateError> {
   const uidMap: Record<string, UserIdentifier> = {};
   for (const prov of state.policy_providers!) {
     const provider = state.authentication_providers![
@@ -435,7 +454,19 @@ async function uploadSecret(
     );
   }
 
-  const escrowMethods: EscrowMethod[] = [];
+  if (!state.recovery_data) {
+    state = await prepareRecoveryData(state);
+  }
+
+  const recoveryData = state.recovery_data;
+  if (!recoveryData) {
+    throw Error("invariant failed");
+  }
+
+  const truthMetadataMap = recoveryData.truth_metadata;
+  const rd = recoveryData.recovery_document;
+
+  const truthPayUris: string[] = [];
 
   for (const truthKey of Object.keys(truthMetadataMap)) {
     const tm = truthMetadataMap[truthKey];
@@ -443,9 +474,6 @@ async function uploadSecret(
     const meth = pol.methods[tm.pol_method_index];
     const authMethod =
       state.authentication_methods![meth.authentication_method];
-    const provider = state.authentication_providers![
-      meth.provider
-    ] as AuthenticationProviderStatusOk;
     const truthValue = await getTruthValue(authMethod, tm.uuid, tm.truth_salt);
     const encryptedTruth = await encryptTruth(
       tm.nonce,
@@ -475,43 +503,37 @@ async function uploadSecret(
       body: JSON.stringify(tur),
     });
 
-    if (resp.status !== 204) {
-      return {
-        code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
-        hint: `could not upload truth (HTTP status ${resp.status})`,
-      };
+    if (resp.status === HttpStatusCode.NoContent) {
+      continue;
     }
-
-    escrowMethods.push({
-      escrow_type: authMethod.type as any,
-      instructions: authMethod.instructions,
-      provider_salt: provider.salt,
-      truth_salt: tm.truth_salt,
-      truth_key: tm.truth_key,
-      url: meth.provider,
-      uuid: tm.uuid,
-    });
+    if (resp.status === HttpStatusCode.PaymentRequired) {
+      const talerPayUri = resp.headers.get("Taler");
+      if (!talerPayUri) {
+        return {
+          code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
+          hint: `payment requested, but no taler://pay URI given`,
+        };
+      }
+      truthPayUris.push(talerPayUri);
+    }
+    return {
+      code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
+      hint: `could not upload truth (HTTP status ${resp.status})`,
+    };
   }
 
-  // FIXME: We need to store the truth metadata in
-  // the state, since it's possible that we'll run into
-  // a provider that requests a payment.
-
-  const rd: RecoveryDocument = {
-    secret_name: secretName,
-    encrypted_core_secret: csr.encCoreSecret,
-    escrow_methods: escrowMethods,
-    policies: policies.map((x, i) => {
-      return {
-        master_key: csr.encMasterKeys[i],
-        uuids: policyUuids[i],
-        salt: policySalts[i],
-      };
-    }),
-  };
+  if (truthPayUris.length > 0) {
+    return {
+      ...state,
+      backup_state: BackupStates.TruthsPaying,
+      payments: truthPayUris,
+    };
+  }
 
   const successDetails: SuccessDetails = {};
 
+  const policyPayUris: string[] = [];
+
   for (const prov of state.policy_providers!) {
     const uid = uidMap[prov.provider_url];
     const acctKeypair = accountKeypairDerive(uid);
@@ -536,26 +558,47 @@ async function uploadSecret(
         body: decodeCrock(encRecoveryDoc),
       },
     );
-    if (resp.status !== 204) {
-      return {
-        code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
-        hint: `could not upload policy (http status ${resp.status})`,
+    if (resp.status === HttpStatusCode.NoContent) {
+      let policyVersion = 0;
+      let policyExpiration: Timestamp = { t_ms: 0 };
+      try {
+        policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
+      } catch (e) {}
+      try {
+        policyExpiration = {
+          t_ms:
+            1000 *
+            Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"),
+        };
+      } catch (e) {}
+      successDetails[prov.provider_url] = {
+        policy_version: policyVersion,
+        policy_expiration: policyExpiration,
       };
+      continue;
     }
-    let policyVersion = 0;
-    let policyExpiration: Timestamp = { t_ms: 0 };
-    try {
-      policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0");
-    } catch (e) {}
-    try {
-      policyExpiration = {
-        t_ms:
-          1000 * Number(resp.headers.get("Anastasis-Policy-Expiration") ?? 
"0"),
-      };
-    } catch (e) {}
-    successDetails[prov.provider_url] = {
-      policy_version: policyVersion,
-      policy_expiration: policyExpiration,
+    if (resp.status === HttpStatusCode.PaymentRequired) {
+      const talerPayUri = resp.headers.get("Taler");
+      if (!talerPayUri) {
+        return {
+          code: TalerErrorCode.ANASTASIS_REDUCER_BACKEND_FAILURE,
+          hint: `payment requested, but no taler://pay URI given`,
+        };
+      }
+      policyPayUris.push(talerPayUri);
+      continue;
+    }
+    return {
+      code: TalerErrorCode.ANASTASIS_REDUCER_NETWORK_FAILED,
+      hint: `could not upload policy (http status ${resp.status})`,
+    };
+  }
+
+  if (policyPayUris.length > 0) {
+    return {
+      ...state,
+      backup_state: BackupStates.PoliciesPaying,
+      payments: policyPayUris,
     };
   }
 
@@ -1048,7 +1091,6 @@ async function updateUploadFees(
   }
   logger.info("updating upload fees");
   const feePerCurrency: Record<string, AmountJson> = {};
-  const coveredProviders = new Set<string>();
   const addFee = (x: AmountLike) => {
     x = Amounts.jsonifyAmount(x);
     feePerCurrency[x.currency] = Amounts.add(
@@ -1058,24 +1100,31 @@ async function updateUploadFees(
   };
   const years = Duration.toIntegerYears(Duration.getRemaining(expiration));
   logger.info(`computing fees for ${years} years`);
+  // For now, we compute fees for *all* available providers.
+  for (const provUrl in state.authentication_providers ?? {}) {
+    const prov = state.authentication_providers![provUrl];
+    if ("annual_fee" in prov) {
+      const annualFee = Amounts.mult(prov.annual_fee, years).amount;
+      logger.info(`adding annual fee ${Amounts.stringify(annualFee)}`);
+      addFee(annualFee);
+    }
+  }
+  const coveredProvTruth = new Set<string>();
   for (const x of state.policies ?? []) {
     for (const m of x.methods) {
       const prov = state.authentication_providers![
         m.provider
       ] as AuthenticationProviderStatusOk;
       const authMethod = 
state.authentication_methods![m.authentication_method];
-      if (!coveredProviders.has(m.provider)) {
-        const annualFee = Amounts.mult(prov.annual_fee, years).amount;
-        logger.info(`adding annual fee ${Amounts.stringify(annualFee)}`);
-        addFee(annualFee);
-        coveredProviders.add(m.provider);
-      }
-      for (const pm of prov.methods) {
-        if (pm.type === authMethod.type) {
-          addFee(pm.usage_fee);
-          break;
-        }
+      const key = `${m.authentication_method}@${m.provider}`;
+      if (coveredProvTruth.has(key)) {
+        continue;
       }
+      logger.info(
+        `adding cost for auth method ${authMethod.challenge} / 
"${authMethod.instructions}" at ${m.provider}`,
+      );
+      coveredProvTruth.add(key);
+      addFee(prov.truth_upload_fee);
     }
   }
   return {
@@ -1097,6 +1146,8 @@ async function enterSecret(
       mime: args.secret.mime ?? "text/plain",
       value: args.secret.value,
     },
+    // A new secret invalidates the existing recovery data.
+    recovery_data: undefined,
   });
 }
 
@@ -1252,7 +1303,9 @@ const recoveryTransitions: Record<
     ...transition("solve_challenge", codecForAny(), solveChallenge),
   },
   [RecoveryStates.ChallengePaying]: {},
-  [RecoveryStates.RecoveryFinished]: {},
+  [RecoveryStates.RecoveryFinished]: {
+    ...transitionRecoveryJump("back", RecoveryStates.ChallengeSelecting),
+  },
 };
 
 export async function reduceAction(
diff --git a/packages/anastasis-core/src/reducer-types.ts 
b/packages/anastasis-core/src/reducer-types.ts
index 08e61cef..56b27898 100644
--- a/packages/anastasis-core/src/reducer-types.ts
+++ b/packages/anastasis-core/src/reducer-types.ts
@@ -67,6 +67,15 @@ export interface ReducerStateBackup {
   secret_name?: string;
   policies?: Policy[];
 
+  recovery_data?: {
+    /**
+     * Map from truth key (`${methodIndex}/${providerUrl}`) to
+     * the truth metadata.
+     */
+    truth_metadata: Record<string, TruthMetaData>;
+    recovery_document: RecoveryDocument;
+  };
+
   /**
    * Policy providers are providers that we checked to be functional
    * and that are actually used in policies.
@@ -198,6 +207,35 @@ export interface ReducerStateRecovery {
   authentication_providers?: { [url: string]: AuthenticationProviderStatus };
 }
 
+/**
+ * Truth data as stored in the reducer.
+ */
+export interface TruthMetaData {
+  uuid: string;
+
+  key_share: string;
+
+  policy_index: number;
+
+  pol_method_index: number;
+
+  /**
+   * Nonce used for encrypting the truth.
+   */
+  nonce: string;
+
+  /**
+   * Key that the truth (i.e. secret question answer, email address, mobile 
number, ...)
+   * is encrypted with when stored at the provider.
+   */
+  truth_key: string;
+
+  /**
+   * Truth-specific salt.
+   */
+  truth_salt: string;
+}
+
 export interface ReducerStateError {
   backup_state?: undefined;
   recovery_state?: undefined;

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]