Invisible Keys Snap : stockage de clés privées multi-cloud

Lauréat de l'ETHLisbon Hackathon

8 min de lecture
Invisible Keys Snap : stockage de clés privées multi-cloud

MetaMask Snaps est la feuille de route pour faire de MetaMask le portefeuille le plus extensible au monde. En tant que développeur, vous pouvez intégrer vos fonctionnalités et vos API à MetaMask de manières totalement nouvelles. Les développeurs Web3 sont au cœur de cette croissance, et cette série vise à mettre en lumière les MetaMask Snaps innovants qui sont construits aujourd'hui.

Invisible Keys Snap

Pourquoi l'avez-vous créé ?

Les comptes détenus en externe sont au cœur de pratiquement chaque dapp ou service dans l'écosystème Ethereum. Cependant, concevoir et implémenter une application de portefeuille à la fois sécurisée et facile à utiliser est loin d'être trivial. En général, pour offrir un contrôle total à l'utilisateur et des niveaux de sécurité élevés, on finit par recourir à des mécanismes complexes et peu pratiques. À l'inverse, une expérience utilisateur simple et agréable ne s'obtient qu'en demandant aux utilisateurs d'abandonner le contrôle de leurs clés.

Avec InvisibleKeys, nous avons tiré parti de l'expérience utilisateur reconnue de MetaMask pour proposer un nouveau compromis entre expérience utilisateur et sécurité, permettant à MetaMask de se connecter à un portefeuille multi-cloud externe innovant.

Comment ? Le portefeuille multi-cloud d'InvisibleKeys stocke les clés privées de l'utilisateur dans deux services de stockage cloud ou plus (par ex. Google Drive, Dropbox, etc.), de telle sorte que même si l'un de ces services est compromis, les clés ne le sont jamais. Cette approche multi-cloud suit un workflow breveté (brevet US20190095628), qui garantit des niveaux de sécurité très élevés tout en permettant la synchronisation multi-appareils.

Pour importer un compte depuis le cloud, MetaMask lit un fichier JSON dans votre Google Drive (ou tout autre service de stockage) contenant un tableau de vos clés publiques. Ensuite, pour signer une transaction, votre clé privée (PK) est temporairement et localement reconstituée dans une application web externe qui reçoit les demandes de signature de MetaMask, signe la transaction et renvoie la transaction signée à MetaMask. Les clés ne sont jamais persistées et n'existent que temporairement en mémoire. De plus, pour préserver le contrôle total de l'utilisateur, la PK n'est jamais accessible par l'extension MetaMask. Même si le portefeuille est déverrouillé ou compromis, la PK ne peut pas être volée.

InvisibleKeys permet aux utilisateurs d'accéder à leur portefeuille depuis n'importe où dans le monde, car tout est stocké dans le cloud, complétant ainsi l'approche traditionnelle de MetaMask et maintenant les standards élevés de MetaMask en matière d'utilisabilité et d'interface UI.

Architecture

InvisibleKeys fonctionne avec deux composants principaux : le portefeuille MetaMask et l'application web externe.

Comme nous avons l'intention de signer la transaction en dehors du portefeuille, nous avons tiré parti de la partie du code dédiée à la connexion/signature des portefeuilles matériels. Au lieu d'envoyer la transaction à signer vers un matériel spécifique, une fenêtre contextuelle s'ouvre avec notre application web où la signature sera exécutée.

Dans l'application web, l'utilisateur s'authentifie avec ses identifiants cloud, la clé privée est récupérée, la transaction est signée et la clé est effacée de la mémoire. Enfin, la fenêtre contextuelle se ferme et l'utilisateur revient au portefeuille MetaMask qui était en état d'attente, dans l'expectative d'une transaction signée à envoyer.

Les deux composants communiquent via l'API web window du navigateur. Cela est suffisant dans ce contexte puisqu'aucune information privée n'est jamais échangée entre les deux.

Modifications de l'interface UI

Tout d'abord, nous avons dû apporter quelques modifications à l'interface UI de MetaMask pour ajouter notre stratégie de connexion InvisibleKeys. Elle est ajoutée de la même manière qu'un nouveau portefeuille matériel pris en charge serait ajouté à l'application.

Voici quelques-unes des modifications apportées au code. Ajout d'un nouvel appareil et type, ainsi que du nouveau bouton pour l'interface UI.

app/scripts/metamask-controller.js

keyringName = InvisibleKeyring.type;
break;
Copier

ui/pages/create-account/connect-hardware/select-hardware.js

  renderConnectToInvisibleButton() {
    return (
      <button
        className={classnames('hw-connect__btn', {
          selected: this.state.selectedDevice === 'invisible',
        })}
        onClick={(_) => this.setState({ selectedDevice: 'invisible' })}
      >
        <img
          className="hw-connect__btn__img"
          src="images/invisible-logo.png"
          alt="Invisible"
        />
      </button>
    );
  }
Copier

Invisible Keys Keyring

Pour connecter différents portefeuilles matériels, la classe Keyring agit comme une interface pour communiquer entre le portefeuille et l'appareil de la marque. Les principales méthodes nécessaires pour importer des comptes et signer des transactions sont : addAccounts() qui retourne un tableau d'adresses, et signTransaction(), qui retourne une transaction signée.

Nous avons implémenté ces méthodes sur InvisibleKeyring. La première ouvre la fenêtre contextuelle et attend un message contenant un tableau d'adresses. La seconde ouvre la fenêtre contextuelle avec la transaction non signée encodée dans un message. Elle attend ensuite une transaction signée encodée, qui sera retournée et envoyée par le contrôleur principal. Pendant l'attente de la fermeture de la fenêtre contextuelle, MetaMask est en état de chargement.

Ces méthodes effectuent des appels à une classe différente InvisibleConnect qui est responsable de la connexion et de la communication avec l'application web.

./InvisibleConnect.js

export default class InvisibleConnect {

  {...}

  const appUrl = 'http://url-to-web-app';

  async getAccountsCloud() {
    let newAccounts = []

    //create a listener for the imported accounts
    window.addEventListener(
      'message',
      event => {
        if (event.data.event_id === 'imported_accounts') {
          newAccounts = event.data.accounts
        }
      },
      false,
    )

    return new Promise(async resolve => {
      const child = window.open(appUrl + '/import')
      const interval = setInterval(() => {
        if (child.closed) {
          //when the popup is closed
          clearInterval(interval)
          this.accounts = newAccounts
          resolve(newAccounts)
        }
      }, 1000)
    })
  }

  async signTxCloud(transaction) {
    let signedTx = {}

    //create a listener for the signedTx
    window.addEventListener(
      'message',
      event => {
        if (event.data.event_id === 'signedTx') {
          signedTx = {
            v: event.data.v,
            r: event.data.r,
            s: event.data.s,
          }
        }
      },
      false,
    )

    return new Promise(async resolve => {
      const child = window.open(appUrl + '/sign') //open popup

      child.postMessage(
        //sends message to the popup
        {
          event_id: 'unsignedTx',
          data: {
            tx: transaction.serialize().toString('hex'),
          },
        },
        '*',
      )

      const interval = setInterval(() => {
        if (child.closed) {
          //when the popup is closed
          clearInterval(interval)
          resolve(signedTx)
        }
      }, 1000)
    })
  }
}
./eth-invisible-keyring.js

import InvisibleConnect from './InvisibleConnect'

class InvisibleKeyring extends EventEmitter {

  {...}

  addAccounts(n = 1) {
    return new Promise((resolve, reject) => {
      this.unlock()
        .then(async _ => {
          const from = this.unlockedAccount
          const to = from + n
          this.accounts = []

          for (let i = from; i < to; i++) {
            const address = InvisibleConnect.getAccounts(i).address

            this.accounts.push(address)
            this.accountIndexes[ethUtil.toChecksumAddress(address)] = i
            this.page = 0
          }
          resolve(this.accounts) //resolve the new accounts
        })
        .catch(e => {
          reject(e)
        })
    })
  }

  signTransaction(address, tx) {
    return new Promise(async (resolve, reject) => {
      try {
        const signedTx = await InvisibleConnect.signTxCloud(tx)
        const txData = tx.toJSON()
        txData.v = ethUtil.addHexPrefix(signedTx.v)
        txData.r = ethUtil.addHexPrefix(signedTx.r)
        txData.s = ethUtil.addHexPrefix(signedTx.s)

        const common = tx.common
        const freeze = Object.isFrozen(tx)
        const feeMarketTransaction = FeeMarketEIP1559Transaction.fromTxData(
          txData,
          {common, freeze},
        )
        resolve(feeMarketTransaction)
      } catch (err) {
        reject(new Error(err))
      }
    })
  }

   {...}
}
Copier

Application externe

L'application comporte deux pages : l'une où les comptes sont importés et l'autre où la transaction est signée.

Dans la première, l'utilisateur s'authentifie sur Google Drive où un fichier contenant ses clés publiques est stocké dans metamask/public.json. Ensuite, après avoir cliqué sur le bouton d'importation, le fichier est récupéré, les clés sont lues et envoyées via l'API web window au portefeuille MetaMask.

const sendAccountsToMetamask = (publicKeys: Address[]) => {
	window.opener.postMessage(
	  {
	    event_id: 'importAccounts',
	    data: {
	      accounts: publicKeys,
	    },
	  },
	  '*'
	);
};

const getPublicKeys = async (): void => {
     const publicKeys: Address[] = await getGoogleDrivePublicKeys();
     sendAccountsToMetamask(publicKeys);
     return;
};
Copier

Dans la seconde, l'utilisateur doit s'authentifier à la fois sur Google Drive et Dropbox, qui étaient les services utilisés pour cet exemple. L'approche est compatible avec tout service de stockage, à condition que ces services soient indépendants et non collusoires. Après authentification, les différentes parties des informations sont récupérées depuis ces services et la clé privée est reconstituée à l'aide de la méthode decrypt(), implémentée conformément au mécanisme décrit dans le brevet susmentionné, et la transaction est signée. Immédiatement après, la clé privée est effacée de la mémoire. Enfin, la transaction signée est renvoyée au portefeuille MetaMask.

Voici le code étape par étape. Seules les parties pertinentes du code sont présentées. Tout ce qui est lié à l'état de l'interface UI ou à la communication avec l'API est omis.

const sendTxToMetamask = (v, r, s) => {
	window.opener.postMessage(
	  {
	    event_id: 'signedTx',
	    data: {
	      v: v,
	      r: r,
	      s: s,
	    },
	  },
	  '*'
	);
};

const signTransaction = async (): void => {
     const trs = TransactionFactory.fromSerializedData(Buffer.from(tx, 'hex'));
     const googleDrivePart = await getGoogleDrivePart();
     const dropboxPart = await getDropboxPart();

     const piecesArray = [
         Buffer.from(googleDrivePart, 'hex'), 
         Buffer.from(dropboxPart, 'hex')
     ];
     const decrypted = decrypt({ data: piecesArray });
     let plainTextPK = Buffer.from(
         new TextDecoder().decode(decrypted.data),
         'hex'
     );

     if (!ethUtil.isValidPrivate(plainTextPK)) {
        alert('Failed to decrypt Private Key');
        return;
     }

     const signedTx = trs.sign(plainTextPK);
     plainTextPK = null; //erase from memory

     const v = ethUtil.stripHexPrefix(ethUtil.bufferToHex(signed_tx.v));
     const r = ethUtil.stripHexPrefix(ethUtil.bufferToHex(signed_tx.r));
     const s = ethUtil.stripHexPrefix(ethUtil.bufferToHex(signed_tx.s));

     if (signed_tx.verifySignature()) {
         sendTxToMetamask(v, r, s);
     } else {
         alert('Signature failed');
     }
    return;
}
Copier

Pouvez-vous nous parler un peu de vous et de votre équipe ?

Nous sommes une équipe enthousiaste d'ingénieurs logiciels basée à Braga, au Portugal, avec une solide expérience dans les systèmes distribués. Nous avons créé Invisible Lab, une startup axée sur la résolution de problèmes complexes dans l'espace blockchain et web3.

Quand avez-vous découvert MetaMask Snaps pour la première fois et quelle a été votre expérience ?

La première fois que nous avons découvert MetaMask Snaps, c'était lors du lancement du SDK. Depuis lors, la technologie s'est considérablement améliorée et continue de le faire.

Qu'est-ce qui différencie MetaMask Snaps des autres portefeuilles ?

Le portefeuille MetaMask Snaps offre la possibilité de construire des fonctionnalités par-dessus tout en conservant un design avec lequel des millions d'utilisateurs interagissent déjà chaque jour.

Parlez-nous de ce que représente la création de Snaps avec MetaMask pour vous et votre équipe ?

Nous avons utilisé une version préliminaire de MetaMask Snaps, qui ne prenait pas en charge toutes les fonctionnalités dont nous avions besoin pour ce projet. Cependant, nous pensons que tout ce que nous avons construit sera possible à réaliser avec Snaps dans un avenir proche, de manière bien plus simple et fluide.

Que représente MetaMask Snaps pour vous ?

Cela représente la personnalisation, l'évolutivité et un déluge de possibilités pour construire de l'innovation par-dessus le portefeuille MetaMask.

Quelles opportunités voyez-vous avec MetaMask Snaps et les possibilités qu'il ouvre pour l'espace Web3 ?

Une opportunité formidable que nous voyons et trouvons intéressante est de construire des stratégies de sécurité supplémentaires. Créer différents niveaux de sécurité en fonction de l'expérience et du niveau de risque auquel chaque utilisateur souhaite être exposé. Cela pourrait avoir un impact significatif sur la réduction de l'exposition des utilisateurs aux problèmes de sécurité et aux piratages technologiques qui ont malheureusement été fréquents dans cet espace. Atténuer ces problèmes est crucial pour l'adoption massive du web3.

Avez-vous des conseils à partager avec les développeurs désireux d'essayer MetaMask Snaps ?

N'ayez pas peur de vous lancer ! Vous serez surpris de ce que vous pouvez accomplir avec MetaMask Snaps. Que ce soit en intégrant de nouvelles blockchains, fonctionnalités ou approches de sécurité !

Construire avec MetaMask Snaps

Pour démarrer avec MetaMask Snaps :

  1. Consultez la documentation développeur

  2. Installez MetaMask Flask

  3. Consultez le guide MetaMask Snaps

  4. Restez connecté avec nous sur Twitter, GitHub discussions, et Discord

Gardez un œil sur notre équipe lors du prochain hackathon près de chez vous ! Bon BUIDLing ⚒️

Avertissement : Les MetaMask Snaps sont généralement développés par des tiers autres que Consensys Software. L'utilisation de MetaMask Snaps développés par des tiers se fait à votre propre discrétion et à vos propres risques, et avec l'accord que vous serez seul responsable de toute perte ou dommage résultant de ces activités. Consensys ne donne aucune garantie expresse ou implicite, qu'elle soit orale ou écrite, concernant les MetaMask Snaps développés par des tiers et décline toute responsabilité pour les MetaMask Snaps développés par des tiers. L'utilisation de logiciels liés à la blockchain comporte des risques, et vous les assumez entièrement lors de l'utilisation de MetaMask Snaps.

Traduit par IA. Peut contenir des erreurs. Veuillez toujours vérifier les informations.

Évaluer la traduction
  • MetaMask
    MetaMask

    MetaMask, anciennement Consensys Software Inc, est la plus grande plateforme financière à détention en propre au monde, offrant aux utilisateurs un espace unique pour détenir, dépenser, épargner et faire fructifier leur argent, aussi bien en crypto qu’en actifs traditionnels. L’entreprise développe la plateforme grand public où tout cela se concrétise, réunissant paiements, épargne, investissement et actifs numériques en une expérience fluide et unifiée. Fort de sa croissance en tant que portefeuille à détention en propre le plus utilisé au monde, MetaMask offre aux utilisateurs un contrôle direct sur leur argent et leurs actifs, en assurant une présence dans environ 190 pays. MetaMask joue un rôle fondateur dans la croissance d’Ethereum depuis 2016. Aujourd’hui, MetaMask est au cœur de l’économie sur la chaîne, en construisant le système d’exploitation de l’Open Money et en offrant aux individus un contrôle total sur leur situation financière.

    Lire tous les articles