Skip to content

Update EVM docs #574

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/bittensor-networks.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The below table presents Bittensor networks and a few details:
| DESCRIPTION | MAINNET | TESTNET | DEVNET |
|:---------------------|:------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------|
| **Chain URL** | wss://entrypoint-finney.opentensor.ai:443 | wss://test.finney.opentensor.ai:443 | wss://dev.chain.opentensor.ai:443 |
| **Example Usage** | Default | `btcli wallet swap_hotkey --subtensor.chain_endpoint wss://dev.chain.opentensor.ai:443` **or** `btcli wallet swap_hotkey --subtensor.network test` | `btcli wallet swap_hotkey --subtensor.chain_endpoint wss://dev.chain.opentensor.ai:443` |
| **Example Usage** | Default | `btcli wallet swap_hotkey --network wss://dev.chain.opentensor.ai:443` | `btcli wallet swap_hotkey --network wss://dev.chain.opentensor.ai:443` |
| **Block processing** | One block every 12 seconds | One block every 12 seconds | One block every 12 seconds |
| **Mainnet Archive** | wss://archive.chain.opentensor.ai:443 | None | None |
| **Mainnet Lite** | wss://lite.chain.opentensor.ai:443 | None | None |
Expand Down
32 changes: 32 additions & 0 deletions docs/evm-tutorials/_create-btcli-wallet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';

export const CreateBtcliPartial = () => (
<>
<h3>Create Wallet with BTCLI</h3>

<ol>
<li>Install BTCLI if you haven't already:
<p>
<code className="language-bash">
pip install bittensor
</code>
</p>
</li>
<li>Create a new wallet:
<p>
<code className="language-bash">
btcli wallet new_coldkey --wallet.name my_wallet
</code>
</p>
</li>
<li>Note down your wallet's SS58 address, which you can get with:
<p>
<code className="language-bash">
btcli wallet overview --wallet.name my_wallet
</code>
</p>
</li>
<li>Your coldkey address will start with "5" and is in SS58 format (for example: <code>5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty</code>).</li>
</ol>
</>
);
23 changes: 23 additions & 0 deletions docs/evm-tutorials/_create-mm-wallet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

export const CreatePartial = () => (
<>
<h3>Create Wallet with MetaMask</h3>

<ol>
<li>Install <a href="https://metamask.io/download/">Metamask wallet</a> browser extension, if you haven't already.</li>
<li>Create a new account or import an existing one.</li>
<li>Add the Bittensor EVM network to MetaMask:
<ul>
<li>Network Name: Bittensor EVM</li>
<li>RPC URL: https://test.chain.opentensor.ai</li>
<li>Chain ID: 945</li>
<li>Currency Symbol: TAO</li>
<li>Block Explorer URL: test.chain.opentensor.ai</li>
</ul>
</li>
<li>Click <b>Save</b>.</li>
<li>Click <b>Switch network</b>.</li>
</ol>
</>
);
34 changes: 34 additions & 0 deletions docs/evm-tutorials/_install.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const InstallPartial = () => (
<>
<h3>Install the EVM Examples repo</h3>
<ol>
<li>
Clone the Opentensor EVM-Bittensor GitHub repo:
<p>
<code className="language-bash">
git clone https://github.com/opentensor/evm-bittensor.git
</code>
</p>
</li>

<li>
Navigate to <code>evm-bittensor</code> directory:
<p>
<code className="language-bash">
cd evm-bittensor
</code>
</p>
</li>

<li>
Install the dependencies:

<code className="language-bash">
npm install
</code>

</li>
</ol>
</>

);
88 changes: 88 additions & 0 deletions docs/evm-tutorials/convert-h160-to-ss58.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: "Convert Ethereum (H160) Address to Substrate (SS58)"
---
import { InstallPartial } from "./_install.mdx";
import { CreatePartial } from "./_create-mm-wallet.mdx";

# Convert Ethereum (H160) Address to Substrate (SS58)

This tutorial demonstrates how to convert between Ethereum (H160) and Substrate (SS58) addresses. This is useful for moving across the boundary between [EVM wallets and Subtensor Wallets on the Bittensor blockchain](./#evm-wallets-and-subtensor-wallets-on-the-bittensor-blockchain).

In what follows, we'll create a wallet in Metamask and convert it's public key to ss58 format in order to target it with a balance transfer using BTCLI.

## Procedure

<CreatePartial />

<InstallPartial />

## Set your config

### Convert Address for Bittensor

Run the conversion script, replacing `ethereumAddress` with your address:

```bash
node convert-address.js
```

Note down the SS58 address output by the script - this is your wallet's Subtensor address on the Bittensor network.

### Transfer TAO to EVM Wallet

Use `btcli` to transfer TAO to your SS58 address. Here we will use test network.

```bash
btcli wallet transfer --destination <your ss58 address> --network test
```
### Verify Balance in MetaMask

1. Open MetaMask
2. Ensure you're connected to the Bittensor EVM network
3. Your TAO balance should now be visible in MetaMask
4. You can now use this wallet for EVM transactions on Bittensor

## Conversion Script

Below is the code used above for the conversion.

**Source code**:
- [EVM examples repo](https://github.com/opentensor/evm-bittensor)
- [Address mapping](https://github.com/opentensor/evm-bittensor/blob/main/examples/address-mapping.js)
- [Convert address](https://github.com/opentensor/evm-bittensor/blob/main/examples/convert-address.js)

```javascript
//convert-address.js

const { convertH160ToSS58 } = require('./address-mapping.js');

async function main() {
const ethereumAddress = "0xbdA293c21DfCaDDAeB9aa8b98455d42325599d23";

const ss58Address = convertH160ToSS58(ethereumAddress);
console.log(`ss58 mirror: ${ss58Address}`);
}

main().catch(console.error);
```

```javascript
// address-mapping.js
function convertH160ToSS58(ethAddress) {
const prefix = 'evm:';
const prefixBytes = new TextEncoder().encode(prefix);
const addressBytes = hexToU8a(ethAddress.startsWith('0x') ? ethAddress : `0x${ethAddress}`);
const combined = new Uint8Array(prefixBytes.length + addressBytes.length);

// Concatenate prefix and Ethereum address
combined.set(prefixBytes);
combined.set(addressBytes, prefixBytes.length);

// Hash the combined data (the public key)
const hash = blake2AsU8a(combined);

// Convert the hash to SS58 format
const ss58Address = encodeAddress(hash, 42); // Network ID 42 for Bittensor
return ss58Address;
}
```
173 changes: 154 additions & 19 deletions docs/evm-tutorials/ed25519-verify-precompile.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
---
title: "Ed25519 Verify Precompile"
title: "Verify Address Precompile"
---

import ThemedImage from '@theme/ThemedImage';
import useBaseUrl from '@docusaurus/useBaseUrl';

# Ed25519 Verify Precompile
# Verify Address Precompile

This precompile is deployed on the subtensor EVM at the address `0x0000000000000000000000000000000000000402`. This precompile allows you to verify an `ed25519` signature.
The Ed25519 Verify Precompile allows EVM smart contracts to verify Ed25519 signatures, which are commonly used in Substrate-based chains like Bittensor. This is essential for bridging identity and ownership between Substrate and EVM ecosystems. For example, you may want to verify coldkey ownership before transferring to someone. EVM functionality doesn't allow transferring directly to a `ss58` address—like the public key of a Bittensor coldkey—because EVM uses the H160 address schema. To bridge the gap, you can use this precompile to prove a claim of ownership. The owner of a coldkey can send an EVM transaction with a signed message, serving as proof of ownership of the coldkey's `ss58` address.

You can use this precompile to verify proof of `ss58` account ownership on the EVM side. For example, you may need to do such verification for an airdrop to TAO owners. While EVM functionality doesn't allow airdropping directly to `ss58` addresses (because EVM is using H160 address schema), one can implement an airdrop via claiming. An owner of `ss58` address eligible for an airdrop can send an EVM transaction which includes the proof of `ss58` address ownership, for example, a signed message, uniquely specific for a given airdrop.
## Prerequisites

For a complete code example see [`ed25519-verify.js`](https://github.com/opentensor/evm-bittensor/blob/main/examples/ed25519-verify.js).
- **Node.js** (v16 or later recommended)
- **npm** or **yarn**
- [Clone the Bittensor EVM examples repo](./install.md)
- [Get set up for using EVM wallet on testnet](./evm-testnet-with-metamask-wallet)
- [Install](./install) the EVM-Bittensor repo, containing scripts and examples.

:::danger Stop. Did you install the dependencies?
Before you proceed, make sure you finished the [Install](./install.md) step.
:::

## Run
## Example

Navigate to the `examples` directory of the EVM-Bittensor repo:

Expand All @@ -33,15 +33,150 @@ To run this precompile, execute:
This example demonstrates how to:

1. Sign an arbitrary message with `ed25519` key.
2. Verify the signature using the precompile contract.
3. Fail the verification of the signature using a corrupted message hash with the precompile contract.
4. Fail the verification of a corrupted signature with the precompile contract.

Any substrate keyring can be initialized as `ed25519` with the same seed phrase or private key as used for signing subtensor transactions, even if they are usually used to create `sr25519` signatures.

The precompile only allows verification of 32-byte messages. However, the arbitrary message can be converted into 32-byte message by calculating the message hash (like it is done in this below example):

```javascript
const messageHash = ethers.keccak256(messageHex); // Hash the message to fit into bytes32
```
[On GitHub](https://github.com/opentensor/evm-bittensor/blob/main/examples/ed25519-verify.js).

2. Verify the signature using the precompile contract.
3. Fail the verification of the signature using the corrupted message hash with the precompile contract.
4. Fail the verification of the corrupted signature with the precompile contract.
<details>
<summary>Full code</summary>
```js
const { ethers } = require('ethers');
const { Keyring } = require('@polkadot/keyring');

// PROTECT YOUR PRIVATE KEYS WELL, NEVER COMMIT THEM TO GITHUB OR SHARE WITH ANYONE
const { rpcUrl } = require('./config.js');

const provider = new ethers.JsonRpcProvider(rpcUrl);

const IED25519VERIFY_ADDRESS = '0x0000000000000000000000000000000000000402';
const IEd25519VerifyABI = [
{
"inputs": [
{ "internalType": "bytes32", "name": "message", "type": "bytes32" },
{ "internalType": "bytes32", "name": "publicKey", "type": "bytes32" },
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
],
"name": "verify",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "pure",
"type": "function"
}
];

async function main() {
const keyring = new Keyring({ type: 'ed25519' });
const myAccount = keyring.addFromUri('//Alice');

//////////////////////////////////////////////////////////////////////
// Generate a signature

// Your message to sign
const message = 'Sign this message';
const messageU8a = new TextEncoder().encode(message);
const messageHex = ethers.hexlify(messageU8a); // Convert message to hex string
const messageHash = ethers.keccak256(messageHex); // Hash the message to fit into bytes32
console.log(`messageHash = ${messageHash}`);
const hashedMessageBytes = hexToBytes(messageHash);

// Sign the message
const signature = myAccount.sign(hashedMessageBytes);
console.log(`Signature: ${bytesToHex(signature)}`);

// Verify the signature locally
const isValid = myAccount.verify(hashedMessageBytes, signature, myAccount.publicKey);
console.log(`Is the signature valid? ${isValid}`);

//////////////////////////////////////////////////////////////////////
// Verify the signature using the precompile contract

const publicKeyBytes = bytesToHex(myAccount.publicKey);
console.log(`publicKeyBytes = ${publicKeyBytes}`);

// Split signture into Commitment (R) and response (s)
let r = signature.slice(0, 32); // Commitment, a.k.a. "r" - first 32 bytes
let s = signature.slice(32, 64); // Response, a.k.a. "s" - second 32 bytes
let rBytes = bytesToHex(r);
let sBytes = bytesToHex(s);
const ed25519Contract = new ethers.Contract(IED25519VERIFY_ADDRESS, IEd25519VerifyABI, provider);
const isPrecompileValid = await ed25519Contract.verify(messageHash, publicKeyBytes, rBytes, sBytes);
console.log(`Is the signature valid according to the smart contract? ${isPrecompileValid}`);

//////////////////////////////////////////////////////////////////////
// Verify the signature for bad data using the precompile contract

let brokenHashedMessageBytes = hashedMessageBytes;
brokenHashedMessageBytes[0] = (brokenHashedMessageBytes[0] + 1) % 0xff;
const brokenMessageHash = bytesToHex(brokenHashedMessageBytes);
console.log(`brokenMessageHash = ${brokenMessageHash}`);
const isPrecompileValidBadData = await ed25519Contract.verify(brokenMessageHash, publicKeyBytes, rBytes, sBytes);
console.log(`Is the signature valid according to the smart contract for broken data? ${isPrecompileValidBadData}`);

//////////////////////////////////////////////////////////////////////
// Verify the bad signature for good data using the precompile contract

let brokenR = r;
brokenR[0] = (brokenR[0] + 1) % 0xff;
rBytes = bytesToHex(r);
const isPrecompileValidBadSignature = await ed25519Contract.verify(messageHash, publicKeyBytes, rBytes, sBytes);
console.log(`Is the signature valid according to the smart contract for broken signature? ${isPrecompileValidBadSignature}`);
}

main().catch(console.error);

function hexToBytes(hex) {
// Remove the '0x' prefix if it exists
if (hex.startsWith('0x')) {
hex = hex.slice(2);
}

// Initialize the array
var bytes = new Uint8Array(hex.length / 2);

// Loop through each pair of characters
for (var i = 0; i < bytes.length; i++) {
// Convert the pair of characters to a byte
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}

return bytes;
}

function bytesToHex(bytes) {
// Initialize the hex string
var hex = [];

// Loop through each byte
for (var i = 0; i < bytes.length; i++) {
// Convert each byte to a hex string and add it to the array
// Ensure it is two digits by padding with a zero if necessary
hex.push((bytes[i] >>> 4).toString(16));
hex.push((bytes[i] & 0xF).toString(16));
}

// Join all hex string parts into one string
return '0x' + hex.join('');
}
```
</details>
## Example Output

```
node ed25519-verify.js
@polkadot/util has multiple versions, ensure that there is only one installed.
Either remove and explicitly install matching versions or dedupe using your package manager.
The following conflicting packages were found:
cjs 12.2.1 node_modules/@polkadot/keyring/node_modules/@polkadot/util/cjs
cjs 13.5.1 node_modules/@polkadot/util/cjs
messageHash = 0xd6ce89c7d4f347455c7dddf19b42e0357edd7587b73b81b384810253c3c3c8ff
Signature: 0x35c3c28c3470ea348343cea4881bd353843236df73a04300261cb86411fe88a05a196842849eb1ef4335b1f171a70e74d2d4c8d3b71ad6a41b6fa48afec85b01
Is the signature valid? true
publicKeyBytes = 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee
Is the signature valid according to the smart contract? true
brokenMessageHash = 0xd7ce89c7d4f347455c7dddf19b42e0357edd7587b73b81b384810253c3c3c8ff
Is the signature valid according to the smart contract for broken data? false
Is the signature valid according to the smart contract for broken signature? false
```
Loading