Foto por energepic.com
Um recurso interessante da Ethereum é a capacidade de provar o consenso diretamente entre as partes. Em outras palavras, você pode validar facilmente que alguém concordou com algo sem a necessidade de terceiros. Como praticamente não há limitação para os dados a serem acordados, isso abre possibilidades infinitas na hora de projetar como os usuários interagem digitalmente.
É importante observar que esse recurso vem da criptografia subjacente à Ethereum e não da própria EVM (Máquina Virtual da Ethereum). Embora essa matemática complexa não esteja no escopo deste artigo, ainda exigimos um entendimento de alto nível da funcionalidade que a criptografia de chave pública/ECDSA permite:
- As mensagens são assinadas usando uma chave privada que resulta em uma assinatura bruta
- Qualquer pessoa com a assinatura bruta e a mensagem não assinada poderá validar a conta que assinou a mensagem usando a chave pública correspondente
- A mensagem não assinada não pode ser extraída da assinatura bruta sem a chave privada correspondente
Você pode conferir este tópico incrível se quiser uma introdução ao ECDSA:
Consequentemente, isso também permite que tais provas sejam tratadas fora da cadeia, o que significa que nenhum gás (e, portanto, nenhum dinheiro) é necessário quando se trata de validar que uma mensagem foi realmente assinada por uma carteira específica. Com esse efeito, temos interações fora da cadeia que são garantidas pela opção de liquidação dentro da cadeia.
Isso abre caminho para casos de uso que são críticos para a adoção convencional:
- Integração do usuário: é improvável que novos usuários de criptomoedas tenham as moedas necessárias para interagir com um contrato inteligente. Ao assinar uma mensagem, o operador pode enviar a transação assinada para a rede, pagando assim as taxas de gás em nome do usuário.
- Identidade descentralizada: assumindo que uma carteira representa um indivíduo, os usuários poderão criar novos relacionamentos sem a necessidade de coordenar financeiramente. Ao validar uma assinatura de mensagem, os usuários podem ter certeza de que a pessoa do outro lado é quem eles dizem ser.
- Dimensionamento por meio de canais: as transações podem ser acumuladas na forma de mensagens assinadas com a liquidação final ocorrendo uma vez na cadeia. Mudanças de estado intermediárias são asseguradas pela segurança da cadeia principal.
- Acordos pré-autorizados: As transações podem ser assinadas com liquidação final decidida por um terceiro. Isso permite o gerenciamento sem custódia dos fluxos de pedidos.
- Assinatura off-line: mesmo em locais onde não há internet, o cálculo da transação ainda pode ocorrer em um computador local. Os usuários podem realizar transações sem a necessidade de uma conexão de rede.
Para este guia, abordaremos a assinatura e validação de mensagens na Ethereum. Isso prioriza a legibilidade da mensagem no momento da assinatura. Observe que isso contrasta com o hash da mensagem, que permite melhor eficiência e segurança na cadeia, além de fornecer uma camada adicional de privacidade. Se você quiser ver um guia de mensagens com hash:
https://medium.com/@kaishinaw/signing-and-verifying-ethereum-hashed-messages-fefa46a746f2
Este guia pressupõe que você tenha familiaridade básica com Express, Ethers.js, Browserify e Metamask. Se você precisar de uma introdução sobre como configurar essas ferramentas, consulte um guia anterior:
https://medium.com/@kaishinaw/connect-metamask-with-ethers-js-fc9c7163fd4d
O repositório do Github para este guia contém alguns arquivos importantes:
-
/client/signing/sign.ts
: Funções do cliente necessárias para assinar a mensagem -
/client/signing/validate.ts
: Funções do cliente que acionam a validação da mensagem no servidor -
/routes/api.ts
: APIs cliente/servidor usadas para comunicar dados - /
server/validate.ts
: Funções do servidor para armazenar e validar os dados
https://github.com/0xKai27/ValidateSigner
Assinar mensagem off-line
Para este guia, o Emissor poderá personalizar sua mensagem para ser assinada com sua própria carteira Metamask. Para determinar a legitimidade da assinatura, a carga útil e a assinatura totalmente estendida serão armazenadas no servidor para validação futura.
Primeiro criamos o formulário de interface do usuário para a mensagem personalizada a ser inserida:
<h2>Assinar mensagem off-line</h2>
<form id="signMessage">
<p>Mensagem:</p>
<input type="text" id="message"/>
<br>
<button>Assinar Mensagem</button>
</form>
Após o envio, nosso código de cliente (/client/signing/sign.ts
) processará a assinatura sequencialmente por:
- Solicitando que o usuário assine a mensagem com
signMessage()
, que prefixa o identificador específico da Ethereum (EIP-191)"\x19Ethereum Signed Message:\n"
e o comprimento do byte da mensagem. Isso elimina o risco de ataques de repetição em outras plataformas EVM. Observe que a mensagem personalizada também é exibida para o usuário entrar na Metamask. Além disso, observe que a Metamask é usada para assinar sem expor as chaves privadas do emissor.
- Gere o formato totalmente expandido da mensagem assinada com
splitSignature()
. A criptografia por trás disso está fora do nosso escopo, mas, efetivamente, essa assinatura é exclusiva da mensagem assinada (ou seja, chave privada e combinação de mensagem assinada) que permite que a mensagem seja validada por outra parte. - Publique a mensagem e a assinatura expandida no servidor para serem salvas para validação futura. Observe que o
unsignedMessage
, osignedMessage
e ofullExpandedSig
foram enviados ao servidor por meio de/api/signedMessage
.
async function signMessage() {
// Faça com que o usuário assine a mensagem com suas chaves privadas no navegador
signedMessage = await ethersSigner.signMessage(unsignedMessage);
console.log(`Mensagem Bruta Assinada:`);
console.debug(signedMessage);
// Obtenha a assinatura totalmente expandida
let fullyExpandedSig: ethers.Signature = ethers.utils.splitSignature(signedMessage);
console.log(`Assinatura Totalmente Expandida:`);
console.debug(fullyExpandedSig);
await fetch('/api/signedMessage', {
method: 'POST',
body: JSON.stringify({unsignedMessage, signedMessage, fullyExpandedSig}),
headers: { 'Content-Type': 'application/json' }
}).then(async (res) => {
const message = await res.text();
console.log(JSON.parse(message).message);
});
};
A lógica para salvar no servidor pode ser encontrada em /routes/api.ts
:
/* POSTAR mensagem assinada e não assinada pelo cliente para ser salva no servidor */
router.post('/signedMessage', async (req, res) => {
await setUnsignedMessage(req.body.unsignedMessage);
await setSignedMessage(req.body.signedMessage);
await setFullyExpandedSig(req.body.fullyExpandedSig);
res.status(200).json({
message: "Salvo com sucesso no servidor!"
});
});
Validar o signatário da mensagem
Como a mensagem assinada agora está armazenada no servidor, agora podemos validar o signatário da mensagem. Para simplificar, supõe-se que o Validador acione a validação a partir da mesma Interface do Usuário. Quando em produção, essa validação de mensagem provavelmente formará um pré-requisito antes que qualquer função específica do aplicativo seja acionada. A lógica de validação principal é processada independentemente pelo servidor (/server/validate.ts
).
Para permitir que a validação seja acionada, criamos um botão de validação, bem como um span que conterá o endereço de assinatura depois de processado:
<h2>Validar o signatário da mensagem</h2>
<button id="validateSignature">Validar Assinatura</button>
<h3>Signatário da mensagem: <span id="messageSigner"></span></h3>
Este botão acionará a validação no servidor por meio da rota /api/validateSignature
especificada em /client/signing/validate.ts
:
validateSignatureButton.addEventListener('click', async () => {
await fetch('/api/validateSignature', {
method: 'GET'
}).then(async (res) => {
const message = await res.text();
messageSignerSpan.innerHTML = JSON.parse(message).messageSigner;
});
});
Seguindo a API especificada, o servidor tentará validar a mensagem com verifyMessage()
, que retorna o endereço que produziu a assinatura assinada. Na prática, compararíamos esse endereço retornado com a chave pública do Emissor, que indicaria a validade da assinatura. Observe também que o verifyMessage()
requer apenas o unsignedMessage
e o fullExpandedSig
, o que implica que a validação pode ser processada sem a necessidade de compartilhar chaves privadas.
async function validateSignature() : Promise<string> {
console.log(`Mensagem não assinada do servidor:`);
console.debug(serverUnsignedMessage);
console.log(`Assinatura Totalmente Expandida do Servidor:`);
console.debug(serverFullyExpandedSig);
let signingAddress: string = ethers.utils.verifyMessage(serverUnsignedMessage, serverFullyExpandedSig);
return signingAddress;
}
Artigo original publicado por Aw Kai Shin. Traduzido por Paulinho Giovannini.
Top comments (0)