WEB3DEV

Cover image for Entendendo Assinaturas Digitais: O Papel de v, r, s na Segurança Criptográfica e Verificação de Assinaturas.
Adriano P. Araujo
Adriano P. Araujo

Posted on

Entendendo Assinaturas Digitais: O Papel de v, r, s na Segurança Criptográfica e Verificação de Assinaturas.

Pré-texto: Este artigo não é destinado a iniciantes, presume-se conhecimento sobre Ethereum e/ou Solidity.

Transações no Ethereum são mensagens assinadas originadas de Contas de Propriedade Externa (CPE). No entanto, há mais nesta simples descrição, pois nem todas essas mensagens são transações válidas. Transações válidas devem conter esses parâmetros obrigatórios: Nonce, Destinatário, Valor, Dados, Preço do Gás, Limite do Gás, ID da Cadeia e v, r, s. Além disso, a transação deve ser assinada usando a chave privada da CPE (e veremos motivo em breve).

Entender o conceito de assinaturas digitais sem entender previamente os componentes dessa assinatura pode ser um pouco complicado para algumas pessoas, então primeiro tentaremos explicar alguns desses conceitos (é claro que esses conceitos são bem detalhados no livro Ethereum).

O Nonce é um acrônimo para Número usado UMA VEZ (do inglês, Number used ONCE). É um número inteiro que acompanha o número de mensagens assinadas e transmitidas de uma conta. Ele é incrementado por transação, e um nonce não pode ser usado mais de uma vez. Qualquer transação assinada com um nonce já utilizado é rejeitada pela rede.

O Valor é a quantidade de ether ou token enviada com a mensagem. Transações contendo apenas valores são conhecidas como "pagamentos".

Os Dados são a carga útil enviada com a transação. Geralmente, são chamadas de função (carga útil contendo o seletor da função) direcionadas para as contas do contrato, já que a maioria das CPE não contém bytecodes e, portanto, não pode lidar com tais chamadas. Transações contendo apenas dados são conhecidas como "invocações". Além disso, as transações também podem conter tanto dados quanto valor, ou nada.

O Chain ID representa a rede blockchain para a qual a transação será enviada. O uso do Chain ID significa que transações criadas para uma blockchain específica não podem ser válidas em outra blockchain. Isso protege as transações contra ataques de repetição, pois as transações se tornam inválidas se forem enviadas para outra rede (consulte EIP-155 para mais informações sobre isso).

O v, r, s são variáveis de assinatura geradas a partir das chaves privadas da CPE e usadas para criar e verificar assinaturas digitais (veremos mais sobre isso em breve).

A Ideia das Chaves Privadas e Públicas

Chaves privadas são um conjunto secreto de inteiros gerados aleatoriamente usados na criptografia para criar chaves públicas e assinar transações. Essas chaves não são necessárias nem transmitidas na rede Ethereum. Elas são propriedades exclusivas e responsabilidades das CPE, e qualquer pessoa que tenha acesso a elas tem acesso a todas as contas geradas a partir daquela chave privada.

Agora, como geramos chaves públicas a partir de chaves privadas? Magia.

O Ethereum utiliza criptografia de chave pública, também conhecida como criptografia assimétrica, para criar pares de chaves pública-privada. A criptografia de curva elíptica (que é uma forma de criptografia assimétrica) é usada para calcular a chave pública a partir da chave privada em uma equação irreversível usando o algoritmo de curva elíptica secp256k1.

Duas coisas a ter em mente:

i) "Uma chave pública é um ponto na curva elíptica, ou seja, é um conjunto de coordenadas x e y que satisfazem a equação da curva elíptica". Portanto, as chaves públicas são dois números unidos, sendo que cada número representa um ponto na curva elíptica.

ii) O algoritmo da curva elíptica usado para calcular esses pontos é uma função unidirecional. Na prática, é muito fácil calcular a chave pública a partir de uma chave privada, mas é impossível reverter o processo e recuperar a chave privada que gerou aquela chave pública. Isso significa que o algoritmo não pode ser revertido para obter uma chave privada que gerou uma chave pública. Nem mesmo os computadores quânticos conseguiram fazer isso.

Serialização de Transações

Agora que entendemos o conceito de chaves públicas e privadas e como elas são geradas, vamos dar um passo adiante e explicar como as transações são serializadas antes de serem transmitidas para a rede.

A serialização é o conceito de preparar mensagens assinadas para transmissão na rede Ethereum usando uma função específica (algoritmo) para criar uma sequência específica de bytes. A função usada para formatar a transação é um padrão amplamente aceito na rede, de modo que transações formatadas com esse padrão são aceitas na blockchain, independentemente da biblioteca, linguagem ou aplicação usada por qualquer nó específico. Na rede Ethereum, o padrão usado para esse propósito é o algoritmo de codificação RLP (Recursive-length prefix algorithm, ou em português,  algoritmo de prefixo de comprimento recursivo).

Conceito de Assinaturas Digitais

Assinaturas digitais são sinônimas das assinaturas oficiais tradicionais. É simplesmente uma maneira de autorizar uma transação ou mensagem na internet. Qualquer transação assinada contém uma assinatura digital, e a assinatura é uma prova de validade dessa mensagem.

As assinaturas digitais são muito importantes porque fornecem uma prova da conta de origem de uma transação (tx.origin em Solidity), e o signatário de tal transação não pode negar que a assinatura se originou de sua conta. As assinaturas digitais também fornecem uma prova de que o conteúdo de uma mensagem ou transação não foi adulterado, pois qualquer alteração no conteúdo da mensagem produzirá uma assinatura totalmente diferente da original.

Como as Assinaturas Digitais São Criadas

Em um nível mais baixo, as assinaturas digitais são geradas a partir do Algoritmo de Assinatura Digital de Curva Elíptica (ECDSA, na sigla em inglês), e esse algoritmo consiste em duas partes. A primeira parte é o algoritmo de criação de assinatura, enquanto a segunda parte é o algoritmo de verificação de assinatura.

Assinaturas digitais são criadas quando uma chave privada assina uma transação já serializada. Na verdade, a transação serializada aqui é o hash Keccak256 da mensagem codificada em RLP. A função matemática para assinar a transação é:

S i g = Fsig(Fkeccak256(m), k)

onde: k = a chave privada de assinatura

m = a mensagem codificada em RLP

Fkeccak256 = função de hash Keccak256

Fsig = o algoritmo de assinatura

Sig = a assinatura resultante

A função Fsig produz uma assinatura (S i g) que gera os dois valores r e s, que são fundamentais na verificação da assinatura. S i g = r, s

(mais sobre isso no livro sobre Ethereum mencionado abaixo).

Encontrei esta explicação do PhD Svetlin Nakov, muito detalhada e útil:

"O algoritmo de assinatura ECDSA (RFC 6979) recebe como entrada uma mensagem msg *+ uma chave privada *privKey ***e produz como saída uma assinatura, que consiste em um par de inteiros {r, s*}.

...A assinatura calculada {r, s} é um par de inteiros, cada um no intervalo [1…n-1]. Ela codifica o ponto aleatório R = k  * G, juntamente com uma prova s, confirmando que o assinante conhece a mensagem h e a chave privada privKey. A prova s pode ser verificada usando a pubKey correspondente."

(Consulte a referência para mais informações ou links).

Verificação de Assinatura

Isso é tratado pela segunda parte do algoritmo ECDSA. Para verificar uma transação, é necessário ter a assinatura (r e s), a transação serializada e a chave pública. Essa chave pública deve corresponder à chave privada usada na assinatura da transação inicialmente. O algoritmo de verificação de assinatura recebe esses componentes listados e retorna um valor booleano dependendo se a assinatura é válida ou não: true para assinatura válida e false para inválida. Os cálculos matemáticos e os processos envolvidos são complexos e não podem ser abordados neste artigo, mas abaixo está um resumo do processo de verificação (novamente, conforme explicado no livro de Svetlin Nakov):

i. Calcule o hash da mensagem, com a mesma função de hash criptográfica usada durante a assinatura.

ii. Calcule o inverso modular da prova de assinatura, ou seja, o inverso da função de geração de assinatura (consulte a função acima).

iii. Recupere o ponto aleatório, r, gerado durante a assinatura a partir da coordenada x.

iv. Gere a partir de R' sua coordenada x: r' = R'.x

v. Calcule o resultado da validação da assinatura comparando se r' == r.

"A ideia geral da verificação da assinatura é recuperar o ponto R' usando a chave pública e verificar se é o mesmo ponto R, gerado aleatoriamente durante o processo de assinatura."

Novamente, este é um resumo simples do conceito completo de geração e verificação de assinaturas, por Sveltin Nakov:

"A assinatura codifica um ponto aleatório R (representado apenas por sua coordenada x) por meio de transformações de curva elíptica usando a chave privada privKey e o hash da mensagem h em um número s, que é a prova de que o assinante conhece a chave privada privKey. A assinatura {r, s} não pode revelar a chave privada devido à dificuldade do problema ECDLP.

A verificação da assinatura decodifica o número de prova s da assinatura de volta para seu ponto original R, usando a chave pública pubKey e o hash da mensagem h, e compara a coordenada x do R recuperado com o valor r da assinatura."

A assinatura é válida se a coordenada x recuperada, r', for a mesma que foi gerada aleatoriamente durante a assinatura, r.

E quanto ao v?

Como já explicado, na geração de chaves públicas usando criptografia de curva elíptica, são geradas duas possíveis chaves públicas em cada instância. Isso ocorre porque o algoritmo de curva elíptica é simétrico em relação ao eixo x, então, para qualquer valor de x que seja gerado, existem dois valores possíveis que se encaixam na curva, um de cada lado do eixo x.

A questão agora é: como esse algoritmo escolhe o valor correto de x para concatenar com o valor de y? É aí que entra v.

De acordo com o Livro Ethereum Yellow, "v é um valor de 1 byte que especifica a paridade e finitude das coordenadas do ponto da curva para o qual r é o valor x".

Para simplificar, v é adicionado à função de verificação de assinatura para ajudar a detectar o valor correto de r entre os dois valores possíveis gerados de R e R'. Se  v  for par,  R é o valor correto; se  v  for ímpar, então é R'.

Para mais esclarecimentos, este é um trecho do livro Ethereum:

"No bloco #2,675,000, a Ethereum implementou o hard fork 'Spurious Dragon', que, entre outras mudanças, introduziu um novo esquema de assinatura que inclui proteção contra repetição de transações (evitando que transações destinadas a uma rede sejam repetidas em outras). Esse novo esquema de assinatura é especificado no EIP-155. Essa alteração afeta a forma da transação e sua assinatura, portanto, atenção deve ser dada à primeira das três variáveis de assinatura (ou seja, v), que assume uma das duas formas e indica os campos de dados incluídos na mensagem da transação sendo hashados.

...A variável especial de assinatura v indica duas coisas: o ID da cadeia e o identificador de recuperação para ajudar a função ECDSArecover a verificar a assinatura. Ela é calculada como um dos valores 27 ou 28, ou como o ID da cadeia duplicado mais 35 ou 36.

...O identificador de recuperação (27 ou 28 nas assinaturas "antigas" ou 35 ou 36 nas transações completas do Spurious Dragon) é usado para indicar a paridade do componente y da chave pública."

A ideia geral da atualização Spurious Dragon era mitigar os desafios de segurança dos ataques de repetição e maleabilidade de assinatura, e isso foi alcançado pela introdução do Chain ID e do identificador de recuperação na variável v.

ECRECOVER

O ecrecover é uma função global em Solidity usada para verificação de assinatura. Ela recebe o hash da mensagem e as variáveis de verificação de assinatura v, r, s, e retorna um endereço. O endereço retornado pode ser comparado com outros endereços para encontrar uma correspondência.

endereço signatário = ecrecover(msgHash, v, r, s);

ALERTA DE SEGURANÇA: A função ecrecover acima é vulnerável a ataques de repetição de assinatura pelos motivos já explicados, portanto, evite usá-la em seus contratos. A biblioteca ECDSA do OpenZeppelin é segura e testada para esse propósito.

Conclusão

A criptografia é a base da tecnologia blockchain. E um sistema público de troca de valores e transações, assim como a blockchain, deve ser 100% seguro e também garantir a integridade, autenticidade e confidencialidade dos dados.

A assinatura digital é uma maneira eficiente de não apenas rastrear a origem das transações, mas também garantir que as transações não sejam comprometidas após serem assinadas e transmitidas. A criação e verificação de assinaturas digitais são possíveis graças às variáveis v, r, s.

Se você achou este artigo interessante, ou talvez se interesse por criptografia, esses recursos estão disponíveis para você.

Recursos:

https://ethereum.stackexchange.com/questions/45461/verify-ecdsa-signature-in-solidity -> Implementações de ECDSA e verificação de assinatura

https://ethereum.stackexchange.com/questions/79302/why-do-we-use-serialize-function-of-ethereumjs-tx-package-before-broadcastin -> Codificação RLP e serialização de transações

https://ethereum.github.io/yellowpaper/paper.pdf -> Ethereum Yellow Paper

https://github.com/ethereumbook/ethereumbook/blob/develop/06transactions.asciidoc#digital-signatures -> Livro Ethereum: Digital Signatures

https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages -> Sveltin Nakov: Assinaturas digitais

https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57 -> Vulnerabilidades de assinatura


Este artigo foi escrito por  Okoli Evans e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Top comments (0)