Pular para o conteúdo

Invisible Keys Snap: armazenamento de chave privada em múltiplas nuvens

Vencedor do ETHLisbon Hackathon

8 min de leitura
Invisible Keys Snap: armazenamento de chave privada em múltiplas nuvens

MetaMask Snaps é o roteiro para tornar a MetaMask a carteira mais extensível do mundo. Como desenvolvedor, você pode trazer seus recursos e APIs para a MetaMask de maneiras totalmente novas. Os desenvolvedores Web3 são o núcleo desse crescimento, e esta série tem como objetivo destacar os novos MetaMask Snaps sendo construídos hoje.

Invisible Keys Snap

Por que você o criou?

As contas de propriedade externa estão no centro de praticamente todo dapp ou serviço no ecossistema Ethereum. No entanto, projetar e implementar uma aplicação de carteira que seja ao mesmo tempo segura e fácil de usar está longe de ser trivial. Normalmente, para oferecer controle total ao usuário e altos níveis de segurança, acabamos recorrendo a mecanismos complexos e pouco práticos. Por outro lado, uma experiência de usuário simples e agradável só é alcançada pedindo aos usuários que abram mão do controle sobre suas chaves.

Com o InvisibleKeys, desenvolvemos sobre a renomada experiência de usuário da MetaMask para propor um novo equilíbrio entre experiência do usuário e segurança, permitindo que a MetaMask se conecte a uma inovadora carteira multi-cloud externa.

Como? A carteira multi-cloud do InvisibleKeys armazena as chaves privadas do usuário em dois ou mais serviços de armazenamento em nuvem (ex.: Google Drive, Dropbox, etc.) de forma que, mesmo que um desses serviços seja comprometido, as chaves nunca sejam. Essa abordagem multi-cloud segue um fluxo de trabalho patenteado (patente US20190095628), que garante níveis muito altos de segurança ao mesmo tempo que permite a sincronização entre múltiplos dispositivos.

Para importar uma conta da nuvem, a MetaMask lê um arquivo JSON no seu Google Drive (ou qualquer outro serviço de armazenamento) com um array das suas chaves públicas. Em seguida, para assinar uma transação, sua chave privada (PK) é temporária e localmente reconstruída em um aplicativo web externo que recebe as solicitações de assinatura da MetaMask, assina a transação e a retorna assinada para a MetaMask. As chaves nunca são persistidas e existem apenas temporariamente na memória. Além disso, para preservar o controle total do usuário, a PK nunca é acessada pela extensão MetaMask. Mesmo que a carteira esteja desbloqueada ou comprometida, a PK não pode ser roubada.

O InvisibleKeys permite que os usuários acessem sua carteira de qualquer lugar do mundo, já que tudo é armazenado na nuvem, complementando a abordagem tradicional da MetaMask e mantendo seus altos padrões de usabilidade e interface.

Arquitetura

O InvisibleKeys funciona com dois componentes principais: a carteira MetaMask e o aplicativo web externo.

Como pretendemos assinar a transação fora da carteira, aproveitamos a parte do código responsável pela conexão e assinatura de carteiras de hardware. Em vez de enviar a transação para ser assinada em um hardware específico, um popup é aberto com nosso aplicativo web, onde a assinatura será executada.

No aplicativo web, o usuário se autentica com suas credenciais de nuvem, a chave privada é recuperada, a transação é assinada e a chave é apagada da memória. Por fim, o popup é fechado e o usuário retorna à carteira MetaMask, que estava em estado de espera aguardando uma transação assinada para ser enviada.

Os dois componentes se comunicam por meio da API web de janela do navegador. Isso é suficiente neste contexto, já que nenhuma informação privada é trocada entre os dois.

Alterações na UI

Primeiramente, precisamos fazer algumas alterações na UI da MetaMask para adicionar nossa estratégia de conexão do InvisibleKeys. Ela é adicionada da mesma forma que uma nova carteira de hardware compatível seria adicionada ao aplicativo.

Estas são algumas das alterações feitas no código: adição de um novo dispositivo e tipo, e o novo botão para a UI.

app/scripts/metamask-controller.js

keyringName = InvisibleKeyring.type;
break;
Copiar

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>
    );
  }
Copiar

Invisible Keys Keyring

Para conectar diferentes carteiras de hardware, a classe Keyring atua como uma interface de comunicação entre a carteira e o dispositivo da marca. Os principais métodos necessários para importar contas e assinar transações são: addAccounts(), que retorna um array de endereços, e signTransaction(), que retorna uma transação assinada.

Implementamos esses métodos no InvisibleKeyring. O primeiro abre o popup e aguarda uma mensagem com um array de endereços. O segundo abre o popup com a transação não assinada codificada em uma mensagem. Em seguida, aguarda uma transação assinada codificada, que será retornada e enviada pelo controlador principal. Enquanto aguarda o fechamento do popup, a MetaMask fica em estado de carregamento.

Esses métodos fazem chamadas a uma classe diferente, InvisibleConnect, que é responsável pela conexão e comunicação com o aplicativo 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))
      }
    })
  }

   {...}
}
Copiar

Aplicativo Externo

O aplicativo tem duas páginas: uma onde as contas são importadas e outra onde a transação é assinada.

Na primeira, o usuário se autentica no Google Drive, onde um arquivo com suas chaves públicas é armazenado em metamask/public.json. Em seguida, ao clicar no botão de importação, o arquivo é buscado, as chaves são lidas e enviadas por meio da API web de janela para a carteira 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;
};
Copiar

Na segunda, o usuário precisa se autenticar tanto no Google Drive quanto no Dropbox, que foram os serviços utilizados neste exemplo. A abordagem é compatível com qualquer serviço de armazenamento, sendo o requisito que sejam serviços independentes e não coniventes entre si. Após a autenticação, as diferentes partes das informações são recuperadas desses serviços e a chave privada é reconstruída usando o método decrypt(), implementado de acordo com o mecanismo descrito na patente mencionada, e a transação é assinada. Imediatamente após isso, a chave privada é apagada da memória. Por fim, a transação assinada é enviada de volta para a carteira MetaMask.

Aqui está o código passo a passo. Apenas as partes relevantes do código são exibidas. Tudo relacionado ao estado da UI ou à comunicação com a API foi omitido.

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;
}
Copiar

Pode nos contar um pouco sobre você e sua equipe?

Somos uma equipe entusiasmada de engenheiros de software com sede em Braga, Portugal, com sólida experiência em sistemas distribuídos. Criamos a Invisible Lab, uma startup focada em resolver problemas complexos no espaço de blockchain e web3.

Quando você teve seu primeiro contato com o MetaMask Snaps e como foi essa experiência?

A primeira vez que tivemos contato com o MetaMask Snaps foi no lançamento do SDK. Desde então, a tecnologia evoluiu significativamente e continua evoluindo.

O que diferencia o MetaMask Snaps de outras carteiras?

A carteira MetaMask Snaps oferece a capacidade de criar recursos sobre ela, mantendo um design com o qual milhões de usuários já interagem todos os dias.

Como é para você e sua equipe desenvolver Snaps com a MetaMask?

Utilizamos uma versão preliminar do MetaMask Snaps, que não suportava todos os recursos de que precisávamos para este projeto. No entanto, acreditamos que tudo o que construímos será possível de ser feito com Snaps em um futuro próximo, de forma muito mais simples e fluida.

O que o MetaMask Snaps significa para você?

Significa personalização, capacidade de atualização e um universo de possibilidades para construir inovação sobre a MetaMask Wallet.

Que oportunidades você enxerga no MetaMask Snaps e nas possibilidades que ele abre para o espaço Web3?

Uma oportunidade incrível que vemos e achamos interessante é a criação de estratégias adicionais de segurança. Criar diferentes níveis de segurança dependendo da experiência e do nível de risco ao qual cada usuário deseja se expor. Isso pode ter um impacto significativo na redução da exposição dos usuários a problemas de segurança e ataques tecnológicos que, infelizmente, têm sido comuns neste espaço. Mitigar esses problemas é fundamental para a adoção em massa do web3.

Algum conselho que gostaria de compartilhar com desenvolvedores interessados em experimentar o MetaMask Snaps?

Não tenha medo de mergulhar de cabeça! Você vai se surpreender com o que pode realizar com o MetaMask Snaps — seja integrando novas blockchains, recursos ou abordagens de segurança!

Desenvolvendo com MetaMask Snaps

Para começar com o MetaMask Snaps:

  1. Confira a documentação para desenvolvedores

  2. Instale o MetaMask Flask

  3. Confira o guia do MetaMask Snaps

  4. Fique conectado conosco no Twitter, nas discussões do GitHub e no Discord

Fique de olho na nossa equipe no próximo hackathon perto de você! Bom BUIDLing ⚒️

Aviso legal: Os MetaMask Snaps são geralmente desenvolvidos por terceiros que não a Consensys Software. O uso de MetaMask Snaps desenvolvidos por terceiros é feito a seu próprio critério e risco, com o entendimento de que você será o único responsável por qualquer perda ou dano resultante de tais atividades. A Consensys não oferece nenhuma garantia expressa ou implícita, seja verbal ou escrita, em relação a MetaMask Snaps desenvolvidos por terceiros, e se isenta de toda responsabilidade por eles. O uso de softwares relacionados a blockchain envolve riscos, e você os assume integralmente ao utilizar o MetaMask Snaps.

Traduzido por IA. Pode conter erros. Por favor, verifique sempre as informações.

Avalie a tradução

Este artigo foi escrito por:

  • MetaMask

    A MetaMask, anteriormente Consensys Software Inc, é a maior plataforma financeira self-custodial do mundo, oferecendo às pessoas um único lugar para guardar, gastar, economizar e fazer seu dinheiro crescer em ativos cripto e tradicionais. A empresa está construindo a plataforma de consumo onde isso acontece, reunindo pagamentos, poupança, investimentos e ativos digitais em uma experiência integrada e fluida. Tendo crescido a partir da carteira self-custodial mais amplamente utilizada do mundo, a MetaMask oferece aos usuários controle direto sobre seu dinheiro e ativos, com alcance em aproximadamente 190 países. A MetaMask desempenhou um papel fundamental no crescimento do Ethereum desde 2016. Hoje, a MetaMask está no centro da economia blockchain, construindo o sistema operacional para o Open Money e colocando as pessoas em total controle de suas vidas financeiras.

    Ler todos os artigos