home / skills / multiversx / mx-ai-skills / multiversx-crypto-verification

multiversx-crypto-verification skill

/skills/multiversx-crypto-verification

This skill enables secure cryptographic operations in MultiversX smart contracts, including hashing and signature verification, to ensure on-chain data

npx playbooks add skill multiversx/mx-ai-skills --skill multiversx-crypto-verification

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
8.1 KB
---
name: multiversx-crypto-verification
description: Cryptographic operations in MultiversX smart contracts. Use when hashing data (SHA256, Keccak256, RIPEMD160), verifying signatures (Ed25519, secp256k1, secp256r1, BLS), or encoding signatures in on-chain logic.
---

# MultiversX Crypto Verification — `self.crypto()` API Reference

Complete reference for hashing and signature verification in MultiversX smart contracts (SDK v0.64+).

## Hashing Functions

All hashing functions take `&ManagedBuffer` (or anything that borrows as `ManagedBuffer`) and return a fixed-size `ManagedByteArray`.

| Method | Return Type | Output Size | Use Case |
|--------|------------|-------------|----------|
| `.sha256(data)` | `ManagedByteArray<A, 32>` | 32 bytes | General purpose hashing, Merkle trees |
| `.keccak256(data)` | `ManagedByteArray<A, 32>` | 32 bytes | Ethereum compatibility, EIP-712 |
| `.ripemd160(data)` | `ManagedByteArray<A, 20>` | 20 bytes | Bitcoin address derivation (rare) |

```rust
// Hash a message
let message = ManagedBuffer::from("data to hash");
let hash: ManagedByteArray<Self::Api, 32> = self.crypto().sha256(&message);

// Hash for Ethereum compatibility
let eth_hash = self.crypto().keccak256(&abi_encoded_data);
```

## Signature Verification

### Critical Distinction: Panic vs Bool

**Panic-based** — Transaction fails immediately on invalid signature. No error handling possible. Use when invalid signature = unauthorized action.

**Bool-based** — Returns `true`/`false`. Contract continues execution. Use when you need graceful error handling or multiple verification attempts.

| Method | Returns | On Invalid Signature |
|--------|---------|---------------------|
| `verify_ed25519(key, message, signature)` | `()` | **Panics** — tx fails with "invalid signature" |
| `verify_bls(key, message, signature)` | `()` | **Panics** |
| `verify_secp256r1(key, message, signature)` | `()` | **Panics** |
| `verify_bls_signature_share(key, message, signature)` | `()` | **Panics** |
| `verify_bls_aggregated_signature(keys, message, signature)` | `()` | **Panics** |
| `verify_secp256k1(key, message, signature)` | `bool` | **Returns false** |
| `verify_custom_secp256k1(key, message, signature, hash_type)` | `bool` | **Returns false** |

### Method Signatures

```rust
// Panic-based (no return value)
fn verify_ed25519(
    &self,
    key: &ManagedBuffer<A>,       // 32-byte public key
    message: &ManagedBuffer<A>,   // arbitrary message
    signature: &ManagedBuffer<A>, // 64-byte signature
)

fn verify_bls(
    &self,
    key: &ManagedBuffer<A>,       // 96-byte BLS public key
    message: &ManagedBuffer<A>,
    signature: &ManagedBuffer<A>, // 48-byte BLS signature
)

fn verify_secp256r1(
    &self,
    key: &ManagedBuffer<A>,       // 33 or 65-byte public key
    message: &ManagedBuffer<A>,
    signature: &ManagedBuffer<A>,
)

fn verify_bls_signature_share(
    &self,
    key: &ManagedBuffer<A>,
    message: &ManagedBuffer<A>,
    signature: &ManagedBuffer<A>,
)

fn verify_bls_aggregated_signature(
    &self,
    keys: &ManagedVec<A, ManagedBuffer<A>>,  // list of BLS public keys
    message: &ManagedBuffer<A>,
    signature: &ManagedBuffer<A>,             // aggregated signature
)

// Bool-based
fn verify_secp256k1(
    &self,
    key: &ManagedBuffer<A>,
    message: &ManagedBuffer<A>,
    signature: &ManagedBuffer<A>,  // DER-encoded or raw (min 2 bytes)
) -> bool

fn verify_custom_secp256k1(
    &self,
    key: &ManagedBuffer<A>,
    message: &ManagedBuffer<A>,
    signature: &ManagedBuffer<A>,
    hash_type: MessageHashType,    // how the message was hashed
) -> bool
```

### MessageHashType Enum

Used with `verify_custom_secp256k1` to specify how the message was pre-hashed:

```rust
pub enum MessageHashType {
    ECDSAPlainMsg,      // Message is not hashed (raw)
    ECDSASha256,        // Message was SHA-256 hashed
    ECDSADoubleSha256,  // Message was double SHA-256 hashed (Bitcoin)
    ECDSAKeccak256,     // Message was Keccak-256 hashed (Ethereum)
    ECDSARipemd160,     // Message was RIPEMD-160 hashed
    ECDSABlake2b,       // Message was Blake2b hashed
}
```

### DER Signature Encoding

Convert raw (r, s) components to DER format for secp256k1:

```rust
fn encode_secp256k1_der_signature(
    &self,
    r: &ManagedBuffer<A>,  // 32-byte r component
    s: &ManagedBuffer<A>,  // 32-byte s component
) -> ManagedBuffer<A>      // DER-encoded signature
```

## Algorithm Selection Guide

| Algorithm | When to Use |
|-----------|-------------|
| **Ed25519** | MultiversX native signatures. Verify user/SC signatures from the chain. Default choice. |
| **secp256k1** | Ethereum/Bitcoin compatibility. Bridge contracts, cross-chain verification. |
| **secp256r1** | NIST P-256 / WebAuthn / Apple Secure Enclave. Passkey-based auth. |
| **BLS** | Validator signatures, multi-sig aggregation, threshold schemes. |
| **BLS aggregated** | Verify a single aggregated signature from multiple validators. |

## Common Patterns

### Ed25519 Signature Gate (MultiversX Native)
```rust
#[endpoint(executeWithSignature)]
fn execute_with_signature(
    &self,
    data: ManagedBuffer,
    signature: ManagedBuffer,
) {
    let signer = self.trusted_signer().get();
    // Panics if invalid — tx reverts automatically
    self.crypto().verify_ed25519(
        &signer,
        &data,
        &signature,
    );
    // Only reached if signature is valid
    self.process_data(&data);
}
```

### Ethereum Signature Verification (Graceful)
```rust
#[endpoint(verifyEthSignature)]
fn verify_eth_signature(
    &self,
    key: ManagedBuffer,
    message: ManagedBuffer,
    signature: ManagedBuffer,
) -> bool {
    // Returns bool — handle failure gracefully
    let valid = self.crypto().verify_custom_secp256k1(
        &key,
        &message,
        &signature,
        MessageHashType::ECDSAKeccak256,
    );
    require!(valid, "Invalid Ethereum signature");
    valid
}
```

### Multi-Validator BLS Aggregated Check
```rust
#[endpoint(verifyValidators)]
fn verify_validators(
    &self,
    validator_keys: ManagedVec<ManagedBuffer>,
    message: ManagedBuffer,
    aggregated_sig: ManagedBuffer,
) {
    // Panics if aggregated signature is invalid
    self.crypto().verify_bls_aggregated_signature(
        &validator_keys,
        &message,
        &aggregated_sig,
    );
}
```

### Hash-Based Commit-Reveal
```rust
#[endpoint(commit)]
fn commit(&self, hash: ManagedByteArray<Self::Api, 32>) {
    let caller = self.blockchain().get_caller();
    self.commitments(&caller).set(hash);
}

#[endpoint(reveal)]
fn reveal(&self, value: ManagedBuffer) {
    let caller = self.blockchain().get_caller();
    let stored_hash = self.commitments(&caller).get();
    let computed_hash = self.crypto().sha256(&value);
    require!(stored_hash == computed_hash, "Hash mismatch");
    self.commitments(&caller).clear();
    self.process_reveal(&caller, &value);
}
```

### DER Encoding for secp256k1
```rust
// When you have raw r,s components (e.g., from an oracle)
let r = ManagedBuffer::from(&r_bytes[..]);
let s = ManagedBuffer::from(&s_bytes[..]);
let der_sig = self.crypto().encode_secp256k1_der_signature(&r, &s);

let valid = self.crypto().verify_secp256k1(&pubkey, &message, &der_sig);
```

## Anti-Patterns

```rust
// BAD: Trying to catch Ed25519 failure — it panics, there's nothing to catch
let result = self.crypto().verify_ed25519(&key, &msg, &sig);
// ← verify_ed25519 returns (), not Result. If invalid, tx is already dead.

// GOOD: Use Ed25519 as a gate (panic is the intended behavior)
self.crypto().verify_ed25519(&key, &msg, &sig);
// Execution continues only if valid

// BAD: Using verify_secp256k1 without checking the bool
self.crypto().verify_secp256k1(&key, &msg, &sig);
// ← Compiles fine but ignores the result! Signature not actually checked.

// GOOD: Always check the bool return
let valid = self.crypto().verify_secp256k1(&key, &msg, &sig);
require!(valid, "Invalid signature");

// BAD: Using wrong hash type for Ethereum signatures
self.crypto().verify_custom_secp256k1(&key, &msg, &sig, MessageHashType::ECDSASha256);
// ← Ethereum uses Keccak256, not SHA256

// GOOD: Match the hash type to the chain
self.crypto().verify_custom_secp256k1(&key, &msg, &sig, MessageHashType::ECDSAKeccak256);
```

Overview

This skill provides a concise reference and practical patterns for cryptographic operations inside MultiversX smart contracts using the self.crypto() API. It covers hashing (SHA-256, Keccak-256, RIPEMD-160), signature verification for Ed25519, secp256k1, secp256r1, and BLS, plus DER encoding for secp256k1. Use it to implement secure on-chain gates, cross-chain signature checks, and aggregated validator verification.

How this skill works

Hashing functions accept ManagedBuffer inputs and return fixed-size ManagedByteArray results suitable for on-chain storage and comparisons. Signature verification exposes panic-based methods (Ed25519, BLS, secp256r1) that abort the transaction on failure, and bool-based secp256k1 methods for graceful handling. A custom secp256k1 verifier accepts a MessageHashType enum so you can match how the message was pre-hashed (Keccak, SHA256, double-SHA256, etc.).

When to use it

  • Protect critical endpoints where an invalid signature must abort the transaction (use panic-based verifiers).
  • Verify Ethereum-style signatures or external ECDSA signatures where you need to handle failure without panicking (use bool-based secp256k1).
  • Implement commit-reveal schemes, Merkle proofs, or any logic requiring deterministic hashes (use sha256/keccak256/ripemd160).
  • Validate WebAuthn or platform passkey assertions using secp256r1.
  • Aggregate validator signatures or threshold schemes using BLS and BLS aggregated verification.

Best practices

  • Choose the verifier type to match your error model: panic-based for gate checks, bool-based for conditional flows.
  • Always check the boolean return of verify_secp256k1 and verify_custom_secp256k1; never ignore the result.
  • Match the MessageHashType to how the message was actually hashed (Ethereum = Keccak256).
  • Use encode_secp256k1_der_signature when you only have raw r/s components before calling secp256k1 verifiers.
  • Store and compare fixed-size ManagedByteArray hashes for commit-reveal and on-chain comparisons to avoid ambiguity.

Example use cases

  • An access-control endpoint that requires an Ed25519 signature from a trusted signer and reverts automatically if invalid.
  • A bridge contract that verifies Ethereum user signatures with verify_custom_secp256k1(MessageHashType::ECDSAKeccak256).
  • A vote aggregation system that verifies a single BLS aggregated signature from multiple validators.
  • A commit-reveal lottery where commitments are stored as sha256 hashes and reveals are validated on-chain.
  • A wallet or passkey verifier that validates secp256r1 signatures coming from platform authenticators.

FAQ

Why do some verifiers panic while others return bool?

Panic-based verifiers are designed for gate checks where invalid signatures should abort execution. Bool-based verifiers let contracts handle failures gracefully or attempt alternatives.

Which hash should I use for Ethereum signatures?

Use Keccak256 and call verify_custom_secp256k1 with MessageHashType::ECDSAKeccak256 to match Ethereum hashing.