Files
pfe-blockchain/team_centralisé/notes_louis.md
2023-03-10 14:45:07 +01:00

11 KiB

Notes Louis

Enumération des attaques sur les bridges

Attaques sur les bridges

Résumé Nomad (article 3)

Nomad est un protocole permettant d'établir des bridges entre Ethereum et d'autres blockchains.

2 types de contrats dans Nomad :

Un contrat 'Home' est le contrat principal où la dApp (decentralized application) est déployée et stockée. Chaque blockchain déploie un contrat 'Replica' qui valide et stocke des messages dans une structure de donnée de type "Merkle tree"

Nomad a déployé un contrat permettant de gérer la réclamation des utilisateurs sur leurs actifs ("bridged assets"). Dans ce contrat, des paramètres d'initialisation ont été indiqués.

function initialize(
    // ...
    bytes32 _committedRoot,
    // ...
) public initializer {
    // ...
    confirmAt[_committedRoot] = 1;//warning!
    // ...
}

Lors d'un déploiement initial, le 'Merkle tree' est vide, avec une valeur égale à 0x0 * 32 et confirmAt[bytes32(0)] est égal à 1.

Puis il y a eu un changement dans l'implémentation de la fonction process()

function process(bytes memory _message) public returns (bool _success) {
    // ...
    require(acceptableRoot(messages[_messageHash]), "!proven");
    // ...
}

Il y a ajout d'un appel de la fonction acceptableRoot() qui fait elle-même appel au mapping "confirmAt[_committedRoot]".

uint256 _time = confirmAt[_root];
if (_time == 0) {
    return false;
}
return block.timestamp >= _time;

Pour tout nouveau message, root = bytes32(0) => confirmAt[bytes32(0)] = 1

acceptableRoot(bytes32(0)) retourne 'True' et permet à des messages d'être traités sans avoir été prouvés au préalable.

Résumé Wormhole (article 5)

Wormhole, le bridge de Solana, a été manipulé pour créditer 120k ETH comme ayant été déposés sur Ethereum, ce qui a permis au pirate de frapper ("mint") l'équivalent en wETH (Wormhole ETH) sur Solana.

  1. En utilisant un SignatureSet créé lors d'une transaction précédente, le pirate a d'abord pu contourner les gardiens de Wormhole mis en place pour vérifier les transferts entre les chaînes et appeler "verify_signatures" sur le bridge principal.

  2. La fonction "verify_signatures" du contrat délègue ensuite la vérification du "SignatureSet" à un programme Secp256k1 distinct. En raison d'une divergence entre 'solana_program::sysvar::instructions' (une sorte de précompilation) et le 'solana_program' utilisé par Wormhole, le contrat n'a pas vérifié correctement l'adresse fournie, et l'attaquant a pu fournir une adresse contenant seulement 0,1 ETH.

  3. En utilisant un compte créé quelques heures plus tôt avec une seule instruction sérialisée correspondant au contrat Sep256k1, l'attaquant a pu falsifier le "SignatureSet", appeler "complete_wrapped" et frapper frauduleusement 120k ETH sur Solana en utilisant la vérification VAA qui avait été créée dans une transaction précédente.

Wormhole

In a nutshell

Protocole générique de passage de messages ("generic message passing protocol") qui connecte plusieurs chaînes (Ethereum, Solana, Binance Smart Chain, Polygon, Avalanche, Algorand, Fantom, Karura, Celo, Acala, Aptos and Arbitrum).

Wormhole émet des messages à partir d'une chaîne qui sont observés par un réseau de nœuds "Guardian" puis vérifiés. Après vérification, ce message est soumis à la chaîne cible pour traitement.

VAA (verified action approval)

Primitive de messagerie de base de Wormhole, contient une en-tête et un corps ("body").

VAA = multisig

(header)
byte        version                  (VAA Version)
u32         guardian_set_index       (Indicates which guardian set is signing)
u8          len_signatures           (Number of signatures stored)
[][66]byte  signatures               (Collection of ecdsa signatures)

(body)
u32         timestamp                 (Timestamp of the block where the source transaction occurred)
u32         nonce                     (A grouping number)
u16         emitter_chain             (Wormhole ChainId of emitter contract)
[32]byte    emitter_address           (Emitter contract address, in Wormhole format)
u64         sequence                  (Strictly increasing sequence, tied to emitter address & chain)
u8          consistency_level         (What finality level was reached before emitting this message)
[]byte      payload                   (VAA message content)

Relayer

"Un processus qui délivre un ou plusieurs VAA(s) à une destination"

  • Trustless
  • Sans privilèges
  1. Effectuer une action sur la chaîne A
  2. Récupérer le VAA résultant du "Guardian Network"
  3. Effectuer une action sur la chaîne B en utilisant le VAA

Guardian

"a set of distributed nodes which monitor state on several blockchains"

Rôle : observer des messages (format VAA) puis signer les charges utiles correspondantes.

Depuis l'outil Explorer, on peut voir qu'il y a 19 guardians pour Wormhole. A première vue, il semble que chaque guardian correspondant à une solution distincte de validateur de blockchain (à fouiller, protocole en commun??).

Chaque guardian travaille individuellement ("isolation") puis mise en commun des signatures (=> multisig) qui forme une preuve qu'un état a été observé et validé par une majorité du réseau Wormhole (> 10 guardians?).

Portal payloads

Charges utiles spécifiques attachées à un VAA depuis une chaîne source pour indiquer à la chaîne cible comment traiter le message Wormhole après vérification.

5 charges utiles au total :

  • Transfer (déclenche la libération de jetons verrouillés)
  • TransferWithPayload (pareil que ci-dessus avec un payload supplémentaire spécifique au domaine)
  • AssetMeta (atteste les metadatas de l'actif)
  • RegisterChain (enregistre le contrat bridge pour une chaîne étrangère)
  • UpgradeContract

Transfer

Les jetons sont transférés d'une chaîne à l'autre à l'aide d'un mécanisme de verrouillage/monnayage ("lockup/mint") et de brûlage/déverrouillage ("burn/unlock").

Pour transférer des jetons de A à B, nous devons verrouiller les jetons sur A et les frapper sur B. Il est important de prouver que les jetons sur A sont verrouillés avant que la frappe puisse avoir lieu sur B. Pour faciliter ce processus, la chaîne A verrouille d'abord les jetons et émet un message indiquant que le verrouillage a été effectué.

"Chain B is agnostic as to how the token on the sending side were locked".

Le protocole se contente de relayer l'événement une fois qu'un nombre suffisant de gardiens en ont attesté l'existence.

u8      payload_id = 1          Transfer
u256    amount                  Amount of tokens being transferred.
u8[32]  token_address           Address on the origin chain.
u16     token_chain             Numeric ID for the origin chain.
u8[32]  to                      Address on the destination chain.
u16     to_chain                Numeric ID for the destination chain.
u256    fee                     Portion of amount paid to a relayer.

Asset

La chaîne A émet un message contenant des métadonnées sur une adresse, que la chaîne B peut stocker afin de connaître le nom, le symbole et la précision décimale de l'adresse d'un jeton.

Obligatoire avant de pouvoir effectuer un transfert !!

u8         payload_id = 2
[32]byte   token_address
u16        token_chain
u8         decimals
[32]byte   symbol
[32]byte   name

Design Wormhole

ERC 5164

Exécution cross-chain (14/06/2022)

Création d'une interface d'éxécution crosschain pour les blockchains basées sur EVM.

Permet à un contrat hébergé sur une chaîne A d'appeler des contrats sur une chaîne B en envoyant un message cross-chain.

2 composants :

  • Message Dispatcher : chaîne d'origine / diffuse message via une couche de transport à un ou plusieurs contrats de type "MessageExecutor"
  • Message Executor : chaîne de destination / exécute les messages "dispatchés"
  1. Un smart contract depuis la chaîne source appelle la fonction dispatchMessage() sur le MessageDispatcher
  2. Le MessageExecutor correspondant sur la chaîne destination va éxécuter le message
  3. N'importe quel smart contract peut recevoir des messages du MessageExecutor
function dispatchMessage(
  uint256 _toChainId,
  address _to,
  bytes calldata _data
) external returns (bytes32) {
  address _executorAddress = _getMessageExecutorAddress(_toChainId);
  _checkExecutor(_executorAddress);

  uint256 _nonce = _incrementNonce();
  bytes32 _messageId = MessageLib.computeMessageId(_nonce, msg.sender, _to, _data);

  _sendMessage(
    _executorAddress,
    MessageLib.encodeMessage(_to, _data, _messageId, block.chainid, msg.sender)
  );

  emit MessageDispatched(_messageId, msg.sender, _toChainId, _to, _data);

  return _messageId;
}
function executeMessage(
  address _to,
  bytes calldata _data,
  bytes32 _messageId,
  uint256 _fromChainId,
  address _from
) external {
  IMessageDispatcher _dispatcher = dispatcher;
  _isAuthorized(_dispatcher);

  bool _executedMessageId = executed[_messageId];
  executed[_messageId] = true;

  MessageLib.executeMessage(_to, _data, _messageId, _fromChainId, _from, _executedMessageId);

  emit MessageIdExecuted(_fromChainId, _messageId);
}

Sources :