Cómo funciona

Recuperación cross device

Cómo un usuario recupera su wallet en un dispositivo nuevo. El modelo Shamir 2 de 3 permite hacer esto sin comprometer la premisa no custodial.

Cuándo se necesita recovery

El flujo de recuperación se dispara en tres situaciones típicas:

En los tres casos la wallet on chain sigue existiendo intacta. Los fondos están seguros. El problema es que el dispositivo actual no tiene F1 ni el passkey necesarios para firmar.

Los tres fragmentos, revisitados

La wallet fue creada partiendo la seed con Shamir 2 de 3. Necesitás 2 fragmentos cualesquiera de estos tres:

En operación normal se usan F1 + F2. En recuperación se usan F2 + F3, porque F1 no está disponible en el device nuevo. La passphrase del usuario descifra F3.

Dos caminos de recuperación

El flow de recovery del kit (<RecoveryFlow />) ofrece dos caminos según cómo se registró el usuario originalmente.

Camino 1: recovery por email

Para users que se registraron con email y password.

  1. Usuario ingresa su email en el flow.
  2. Backend envía un OTP de 6 dígitos al email via Amazon SES.
  3. Usuario ingresa el OTP y su passphrase de recovery en el flow.
  4. Backend verifica el OTP contra el hash bcrypt, emite un recoveryJwt con TTL 5 minutos.
  5. SDK descarga F2 y F3 (envueltos en session key efímera).
  6. SDK deriva la recoveryKey con PBKDF2(passphrase, salt, 600k), descifra F3.
  7. SDK reconstruye la seed usando Shamir(F2, F3).
  8. SDK genera un passkey nuevo en el device actual con la extensión PRF.
  9. SDK genera una seed nueva, la parte con Shamir, cifra los nuevos F1, F2, F3.
  10. SDK firma una transacción rotate_signer con la seed vieja, la manda al backend, se ejecuta on chain.
  11. Backend actualiza F2 y F3 en DynamoDB con los nuevos fragmentos. SDK guarda F1 nuevo en IndexedDB local.

Camino 2: recovery via Google

Para users que se registraron con Google.

  1. Usuario clickea "Continuar con Google" en el flow.
  2. Sesión Cognito federada se establece.
  3. Backend envía OTP al email del Google account.
  4. Usuario ingresa OTP y passphrase de recovery.
  5. El resto es idéntico al Camino 1 desde el paso 4.
i
Por qué email OTP en ambos casos

Incluso en el flow Google, mandamos OTP al email como segundo factor. Esto protege contra el escenario donde alguien obtiene control del Google account del user (via compromiso de sesión OAuth) pero no del email inbox.

Qué pasa on chain

El paso crítico del recovery es la rotación del signer on chain. El Smart Account v3 tiene una función rotate_signer que reemplaza el owner ed25519 pubkey y el passkey secp256r1 pubkey.

La firma de esta transacción se hace con la seed vieja (recién reconstruida por Shamir F2 + F3). Después de la rotación:

Como side effect, la G address bridge (dirección Stellar classic asociada) también se invalida porque su master key es la seed vieja. El SDK detecta esto y hace bootstrap de una G address nueva la próxima vez que se necesite (típicamente al hacer un swap SDEX).

Rotate multi tx en wallets con muchas rules

A partir de Smart Account v3.2.0 el SDK maneja automáticamente wallets con 4 o más context rules activas (típicamente cuando el usuario activó XLM, USDC y otras policies). En esas wallets el rotate_signer atómico escribe más de 132KB en el footprint Soroban y excede el cap de protocol 27.

Cuando el backend detecta esta situación, devuelve un error tipado RotateWriteCapExceededError con la lista de rotatableRuleIds. El SDK reacciona particionando la rotación en varias transacciones:

  1. El SDK agrupa las rules en batches de 2 y ejecuta una tx rotate_signer_partial(rule_ids, new_owner, new_secp) por batch. Cada tx queda holgadamente bajo el cap.
  2. Cuando todos los batches fueron aplicados, el SDK ejecuta una tx final finalize_rotation(new_owner, new_secp, new_email_commitment) que persiste el nuevo email commitment y emite el evento SignerRotated.
  3. El backend recibe la firma del finalize_rotation y en la misma llamada persiste todos los nuevos fragments F1, F2, F3 en DynamoDB.

El caller del hook recovery.completeRecovery(...) no ve la diferencia entre el path atómico y el multi tx. Recibe el mismo shape de respuesta con walletAddress, txHash del finalize_rotation y status. Wallets con 3 rules o menos siguen usando el path atómico legacy, sin overhead ni endpoints adicionales.

i
Fee cubierto por el relayer

Cada tx del multi-tx cobra un fee que Accesly paga desde el relayer fund. En la práctica una wallet con 4 rules dispara 3 tx (2 batches + finalize) en lugar de 1, pero el usuario nunca paga XLM.

El rol de la passphrase

La passphrase de recovery es la única cosa que el usuario tiene que recordar. Es el gate de seguridad del F3.

Sin la passphrase, la recuperación es imposible. Ni Accesly, ni AWS, ni el integrador tiene forma de derivar la recoveryKey sin conocer la passphrase. Es literalmente PBKDF2 con 600 mil rondas de un secreto que solo el user sabe.

!
Comunicá esto claramente al usuario

Al momento de creación de wallet, el usuario elige una passphrase. Si la olvida, y además pierde el device, los fondos son inaccesibles. No hay backdoor. Es la contra parte del modelo no custodial. Tu UI tiene que dejarlo brutalmente claro.

Anti abuse en recovery

El endpoint POST /recovery/otp/request tiene rate limiting:

Y en POST /recovery/otp/verify:

Diseño del RecoveryFlow del kit

El componente <RecoveryFlow /> del kit maneja los dos caminos con un state machine interno:

import { RecoveryFlow } from '@accesly/react/kit';
import { useNavigate } from 'react-router-dom';

export function RecoverPage() {
  const navigate = useNavigate();
  return (
    <RecoveryFlow
      onDone={() => navigate('/wallet')}
      onCancel={() => navigate('/')}
    />
  );
}

El componente renderiza 4 pantallas: selector de método (email vs Google), input de email, input de OTP + passphrase, y confirmación. Toda la lógica de descargar fragments, reconstruir seed, rotar signer y firmar la nueva config está encapsulada en el hook recovery.finalize.

Casos especiales

Wallet huérfana detectada al signup

Si un usuario intenta hacer signup con un email que ya tiene wallet on chain (típicamente porque su device tenía credential pero se lo borraron, y ahora está en device nuevo), el SDK detecta la situación y devuelve WalletAlreadyExistsError. El kit <CreateWalletFlow /> muestra pantalla "Ya tenés una wallet" con CTA a <RecoveryFlow />.

Recovery múltiple

Un usuario puede hacer recovery cuantas veces necesite. Cada recovery rota el signer on chain. Cada rotación tiene un fee on chain que Accesly cubre con el relayer fund.

Cognito dual users con mismo email

Escenario edge case: un usuario se registró primero con email y password, después olvidó y se registró con Google (mismo email). Cognito los trata como users distintos con sub distintos. El flow de recovery incluye el cognitoSub en el recoveryJwt para asegurar que la rotación afecta exactamente al usuario Cognito autenticado, no a un dual user del mismo email.

Siguientes pasos