> For the complete documentation index, see [llms.txt](https://developers.mtpelerin.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.mtpelerin.com/integration-guides/parameters-and-customization/automating-the-end-user-address-validation/how-to-test.md).

# How to test

Check that your address validation automation works

### Test seed

Every vector on this page is derived from a single seed phrase, so you can check your implementation on all three chain families with one wallet.

```
seed: bamboo feed assist glove soda merry medal vanish almost solid bean loop
code: 1234
message: MtPelerin-1234
```

| Chain                  | Derivation path     | Address                                                          |
| ---------------------- | ------------------- | ---------------------------------------------------------------- |
| Ethereum               | `m/44'/60'/0'/0/0`  | `0xEa22e16EA50A43092853329F3cEEa0825Cb9B03e`                     |
| Bitcoin, native SegWit | `m/84'/0'/0'/0/0`   | `bc1qjq907v02ra3pde52zza345c7cj52guwuucz527`                     |
| Bitcoin, Taproot       | `m/86'/0'/0'/0/0`   | `bc1p2uj2zul3vl87grth4ju9fqmam5gxsncr4w9sdzvdv44sr90v657s4ey7a0` |
| Bitcoin, nested SegWit | `m/49'/0'/0'/0/0`   | `39MJF9h9YUcHtmgB9yfpqhPcuVFA848unL`                             |
| Bitcoin, legacy        | `m/44'/0'/0'/0/0`   | `17RudQJ5mepFTCoqErbuwRkey6hPWULcnb`                             |
| Tezos                  | `m/44'/1729'/0'/0'` | `tz1dnCbNYHDoxmHss9kEQVjWQj6GvYX4gYp5`                           |

{% hint style="danger" %}
The seed phrase and private keys on this page are for test purposes only, **do not** use them with real funds.
{% endhint %}

{% hint style="info" %}
The same code is reused across all the vectors below to keep them comparable. In production you must generate a different code for each address.
{% endhint %}

## EVM

### What goes in `hash`

The raw bytes returned by `personal_sign`, encoded in base64. The widget accepts any EVM network, and smart contract wallets are supported through the `chain` parameter, see below.

### Test vector

```
seed: bamboo feed assist glove soda merry medal vanish almost solid bean loop
derivation path: m/44'/60'/0'/0/0
private key: 78ba65f1cc9427fab632340ae4d705b1485fba9f73ab5a24816907d36d5729e9
address: 0xEa22e16EA50A43092853329F3cEEa0825Cb9B03e
code: 1234
hash: yrXNJSmMc4wvVyKEzN4cEmLTvEaridjqTULZAfMwYAMM5PgBz4fCoIWNLr5NwKhxOYiPpI2vhMlKCihWadUw5xs=
```

A second vector, from a standalone private key with no seed phrase:

```
private key: 4142e80a872531fd1055f52ccab713d4c7f1eee28c33415558e74faeb516de2b
address: 0x270402aeB8f4dAc8203915fC26F0768feA61b532
code: 1234
hash: /37KcpG6mEp+1oAan8/HLEvcfZFXUi6kTOxTHNjD3ZloxS8DL70v7lCmXiEyDOATm4hvewMzBO2d1n25QdJ8WBw=
```

### Browser, Metamask

```js
const toBase64 = (u8) => btoa(String.fromCharCode.apply(null, u8));

const fromHexString = (hexString) =>
  Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));

/* Example for private key 78ba65f1cc9427fab632340ae4d705b1485fba9f73ab5a24816907d36d5729e9 */
const code = "1234";
const message = "MtPelerin-" + code;
const address = "0xEa22e16EA50A43092853329F3cEEa0825Cb9B03e"; // ethereum address

window.ethereum
  .request({
    method: "personal_sign",
    params: [message, address],
  })
  .then((hash) => {
    // hash should be 0xcab5cd25298c738c2f572284ccde1c1262d3bc46ab89d8ea4d42d901f33060030ce4f801cf87c2a0858d2ebe4dc0a87139888fa48daf84c94a0a285669d530e71b
    const base64Hash = toBase64(fromHexString(hash.replace("0x", "")));
    // base64Hash should be yrXNJSmMc4wvVyKEzN4cEmLTvEaridjqTULZAfMwYAMM5PgBz4fCoIWNLr5NwKhxOYiPpI2vhMlKCihWadUw5xs=
    return base64Hash;
  })
  .catch(console.log);
```

### NodeJS, ethers

```js
const ethers = require("ethers");

/* Example for private key 78ba65f1cc9427fab632340ae4d705b1485fba9f73ab5a24816907d36d5729e9 */
const code = "1234";
const message = "MtPelerin-" + code;

const wallet = new ethers.Wallet(
  "0x78ba65f1cc9427fab632340ae4d705b1485fba9f73ab5a24816907d36d5729e9",
);

wallet
  .signMessage(message)
  .then((hash) => {
    // hash should be 0xcab5cd25298c738c2f572284ccde1c1262d3bc46ab89d8ea4d42d901f33060030ce4f801cf87c2a0858d2ebe4dc0a87139888fa48daf84c94a0a285669d530e71b
    const base64Hash = Buffer.from(hash.replace("0x", ""), "hex").toString(
      "base64",
    );
    // base64Hash should be yrXNJSmMc4wvVyKEzN4cEmLTvEaridjqTULZAfMwYAMM5PgBz4fCoIWNLr5NwKhxOYiPpI2vhMlKCihWadUw5xs=
    return base64Hash;
  })
  .catch(console.log);
```

### Smart contract wallets

To validate the address of a smart contract wallet (EIP-1271), add the `chain` parameter so the on-chain `isValidSignature` call targets the right network. Counterfactual wallets that are not deployed yet are also accepted, through EIP-6492.

Accepted values: `arbitrum_mainnet`, `avalanche_mainnet`, `base_mainnet`, `bsc_mainnet` (BNB Chain), `celo_mainnet`, `mainnet` (Ethereum), `matic_mainnet` (Polygon), `optimism_mainnet`, `rsk_mainnet` (Rootstock), `sonic_mainnet`, `tempo_mainnet`, `xdai_mainnet` (Gnosis Chain), `zksync_mainnet`.

### Ready to use URL

```
https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=mainnet&bdc=ETH&addr=0xEa22e16EA50A43092853329F3cEEa0825Cb9B03e&code=1234&hash=yrXNJSmMc4wvVyKEzN4cEmLTvEaridjqTULZAfMwYAMM5PgBz4fCoIWNLr5NwKhxOYiPpI2vhMlKCihWadUw5xs%3D
```

## Bitcoin

### What goes in `hash`

The base64 signature of the message, sent as is. Two schemes are accepted:

* **BIP-322 simple**, for native SegWit (`bc1q...`), nested SegWit (`3...`) and single-key-spend Taproot (`bc1p...`). This is what modern wallets return.
* **BIP-137**, the legacy `signmessage` scheme, for legacy addresses (`1...`).

Unlike the EVM flow, there is no hex to base64 conversion to do: Bitcoin wallets already return base64.

### Test vector, native SegWit

```
seed: bamboo feed assist glove soda merry medal vanish almost solid bean loop
derivation path: m/84'/0'/0'/0/0
private key: 5c2f8e97befd8fd4daa3631a0dc773570c0cba4d5350d33d38d8815cc28a15d0
private key (WIF): KzJucfgj6tWqP1qVupcUTm2T4Db46KYsLLoF2QSJ2MCT9RhfNMyX
address: bc1qjq907v02ra3pde52zza345c7cj52guwuucz527
code: 1234
hash: AkgwRQIhAJsDTCOB8OeNDhByqVVCSKRVWkZO3LdRE3+huDhI4XujAiAHi7aXErGzX5kiwHi0xXEHnqGNi1cpF1DggvqDXuSURwEhA/X/AAai8u+YLgUkxuYmfMwRDHHE+s7m6lTODQpcQGEv
```

### Test vector, Taproot

```
derivation path: m/86'/0'/0'/0/0
private key (WIF): L5BQYeEqmSdV5UDpiV5Db8jR5J28Du7HHnfuDFgnAqoJXYhvvDHA
address: bc1p2uj2zul3vl87grth4ju9fqmam5gxsncr4w9sdzvdv44sr90v657s4ey7a0
code: 1234
hash: AUElkPZD8BL68hMi7o1eM2+WX8XoF8WwA18uLXc1KgpXBrLrpcWQ0fvdTVGIePTGOFiC/fNyA5Ns87piWXHdU4OyAQ==
```

> **Note** Taproot is the one exception on this page: BIP-340 signatures embed auxiliary random data, so signing the same message twice with the same key gives two different signatures. Yours will not match the value above, and both are valid. Every other vector on this page is deterministic and must match byte for byte.

### Test vector, nested SegWit

```
derivation path: m/49'/0'/0'/0/0
private key (WIF): KycaxNd2MjHDCe1P26i1PiyhVoiKzyapKqu7VWYYEpQ6AjLWhWwy
address: 39MJF9h9YUcHtmgB9yfpqhPcuVFA848unL
code: 1234
hash: AkgwRQIhAO84IDt+0YyoZggQkG7nm2tugNw0+UYfpZFb0kkFGbboAiAFlZlU9yXPfinHsByTYzxzYtoP1dDiyfDiTjXe4aq+2wEhAlNCvmA1hQ719xkhycNraBbBvLgF1aNDgkUcXctdxE0s
```

### Test vector, legacy

```
derivation path: m/44'/0'/0'/0/0
private key: f9500d2cd143a028a05295118a38d3bc0dc2c678ac7051427e19be41be6818ce
private key (WIF): L5aLoPgSkahVakK2ZBKiUcEexZz5YWF771HgWvC7X9ftYzNHoJ7p
address: 17RudQJ5mepFTCoqErbuwRkey6hPWULcnb
code: 1234
hash: IOne2AEOarx21gYSFXZf+FA3AribTL0rfYDZOGcdrTDgRwjkt3c8ODEVKNzhkArUOOYWbd4LeB3LTveO1QaesXg=
```

### Browser, wallet extensions

Wallets that implement message signing return the base64 signature directly, so the value goes straight into `hash` once URL encoded. Ask for the BIP-322 scheme when the wallet lets you choose:

```js
/* Example for address bc1qjq907v02ra3pde52zza345c7cj52guwuucz527 */
const code = "1234";
const message = "MtPelerin-" + code;
const address = "bc1qjq907v02ra3pde52zza345c7cj52guwuucz527";

const hash = await window.unisat.signMessage(message, "bip322-simple");
// hash should be AkgwRQIhAJsDTCOB8OeNDhByqVVCSKRVWkZO3LdRE3+huDhI4XujAiAHi7aXErGzX5kiwHi0xXEHnqGNi1cpF1DggvqDXuSURwEhA/X/AAai8u+YLgUkxuYmfMwRDHHE+s7m6lTODQpcQGEv

const url =
  "https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=bitcoin_mainnet&bdc=BTC&addr=" +
  address +
  "&code=" +
  code +
  "&hash=" +
  encodeURIComponent(hash);
```

### NodeJS, bip322-js

```js
const { Signer } = require("bip322-js");

/* Example for private key KzJucfgj6tWqP1qVupcUTm2T4Db46KYsLLoF2QSJ2MCT9RhfNMyX */
const code = "1234";
const message = "MtPelerin-" + code;
const address = "bc1qjq907v02ra3pde52zza345c7cj52guwuucz527";

const hash = Signer.sign(
  "KzJucfgj6tWqP1qVupcUTm2T4Db46KYsLLoF2QSJ2MCT9RhfNMyX",
  address,
  message,
);
// hash should be AkgwRQIhAJsDTCOB8OeNDhByqVVCSKRVWkZO3LdRE3+huDhI4XujAiAHi7aXErGzX5kiwHi0xXEHnqGNi1cpF1DggvqDXuSURwEhA/X/AAai8u+YLgUkxuYmfMwRDHHE+s7m6lTODQpcQGEv

console.log(encodeURIComponent(hash));
```

For a legacy address, `bitcoinjs-message` produces the accepted BIP-137 form:

```js
const bitcoinMessage = require("bitcoinjs-message");

const privateKey = Buffer.from(
  "f9500d2cd143a028a05295118a38d3bc0dc2c678ac7051427e19be41be6818ce",
  "hex",
);
const hash = bitcoinMessage
  .sign("MtPelerin-1234", privateKey, true)
  .toString("base64");
// hash should be IOne2AEOarx21gYSFXZf+FA3AribTL0rfYDZOGcdrTDgRwjkt3c8ODEVKNzhkArUOOYWbd4LeB3LTveO1QaesXg=
```

### Ready to use URLs

Native SegWit:

```
https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=bitcoin_mainnet&bdc=BTC&addr=bc1qjq907v02ra3pde52zza345c7cj52guwuucz527&code=1234&hash=AkgwRQIhAJsDTCOB8OeNDhByqVVCSKRVWkZO3LdRE3%2BhuDhI4XujAiAHi7aXErGzX5kiwHi0xXEHnqGNi1cpF1DggvqDXuSURwEhA%2FX%2FAAai8u%2BYLgUkxuYmfMwRDHHE%2Bs7m6lTODQpcQGEv
```

Taproot:

```
https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=bitcoin_mainnet&bdc=BTC&addr=bc1p2uj2zul3vl87grth4ju9fqmam5gxsncr4w9sdzvdv44sr90v657s4ey7a0&code=1234&hash=AUElkPZD8BL68hMi7o1eM2%2BWX8XoF8WwA18uLXc1KgpXBrLrpcWQ0fvdTVGIePTGOFiC%2FfNyA5Ns87piWXHdU4OyAQ%3D%3D
```

Nested SegWit:

```
https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=bitcoin_mainnet&bdc=BTC&addr=39MJF9h9YUcHtmgB9yfpqhPcuVFA848unL&code=1234&hash=AkgwRQIhAO84IDt%2B0YyoZggQkG7nm2tugNw0%2BUYfpZFb0kkFGbboAiAFlZlU9yXPfinHsByTYzxzYtoP1dDiyfDiTjXe4aq%2B2wEhAlNCvmA1hQ719xkhycNraBbBvLgF1aNDgkUcXctdxE0s
```

Legacy:

```
https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=bitcoin_mainnet&bdc=BTC&addr=17RudQJ5mepFTCoqErbuwRkey6hPWULcnb&code=1234&hash=IOne2AEOarx21gYSFXZf%2BFA3AribTL0rfYDZOGcdrTDgRwjkt3c8ODEVKNzhkArUOOYWbd4LeB3LTveO1QaesXg%3D
```

> **Info** Base64 signatures contain `+`, `/` and `=`, which all have a meaning in a query string. If your signature is refused, check that they were encoded as `%2B`, `%2F` and `%3D`.

## Tezos

### What the wallet signs

Tezos does not sign the message directly. It signs the **Michelson PACK form** of the message prefixed with `Tezos Signed Message:` , that is the two bytes `05 01`, the byte length on 4 bytes big-endian, then the UTF-8 bytes.

For the code `1234`, the bytes handed to the wallet are:

```
05010000002454657a6f73205369676e6564204d6573736167653a204d7450656c6572696e2d31323334
```

### The armored block

The `hash` parameter is not a bare signature: it is the standard Tezos armored block, **exactly 6 lines** joined by `\n`. A missing line, an extra line, a trailing newline or `\r\n` line endings are all rejected.

```
-----BEGIN TEZOS SIGNED MESSAGE-----
Tezos Signed Message: MtPelerin-1234
-----BEGIN SIGNATURE-----
edpkus9ckWtwxNi7NqZqGT2bo1VGxyRkCvaV5zkms7rkeWUV532PhU
edsigtzYwqm72h1cBbWLSMUVEu3t7bUT2RqBMppLdtVGH3VqKhppz96YE6yQSS4GKKkB6nHCRXmdiukHkw3Puy5Zd5b2rvaosRG
-----END TEZOS SIGNED MESSAGE-----
```

* Line 2: `Tezos Signed Message:` followed by `MtPelerin-{code}`, byte for byte.
* Line 4: the signer's public key.
* Line 5: the signature.

The public key on line 4 **must correspond to the address passed in `addr`**. A block signed by a different key is rejected, even when the signature itself is valid.

### Test vector

```
seed: bamboo feed assist glove soda merry medal vanish almost solid bean loop
derivation path: m/44'/1729'/0'/0'
secret key: edskRvYoDB1pSFUcKR9L1mjevnWtSNupR8C3pUQ5uaEygTbPee95tx9kHregYmdY1Mn12J7ii6QQfAeKVT6bkgYmyRkKCiTi19
public key: edpkus9ckWtwxNi7NqZqGT2bo1VGxyRkCvaV5zkms7rkeWUV532PhU
address: tz1dnCbNYHDoxmHss9kEQVjWQj6GvYX4gYp5
code: 1234
signed message: Tezos Signed Message: MtPelerin-1234
packed payload: 05010000002454657a6f73205369676e6564204d6573736167653a204d7450656c6572696e2d31323334
signature: edsigtzYwqm72h1cBbWLSMUVEu3t7bUT2RqBMppLdtVGH3VqKhppz96YE6yQSS4GKKkB6nHCRXmdiukHkw3Puy5Zd5b2rvaosRG
```

### Browser, Beacon SDK

```js
import { SigningType } from "@airgap/beacon-sdk";

/* Example for address tz1dnCbNYHDoxmHss9kEQVjWQj6GvYX4gYp5 */
const code = "1234";
const signedMessage = "Tezos Signed Message: MtPelerin-" + code;

// Michelson PACK: 05 01 + byte length on 4 bytes big-endian + UTF-8 bytes
const bytes = new TextEncoder().encode(signedMessage);
const payload =
  "0501" +
  bytes.length.toString(16).padStart(8, "0") +
  [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
// payload should be 05010000002454657a6f73205369676e6564204d6573736167653a204d7450656c6572696e2d31323334

const { address, publicKey } = await wallet.client.getActiveAccount();
const { signature } = await wallet.client.requestSignPayload({
  signingType: SigningType.MICHELINE,
  payload,
});
// signature should be edsigtzYwqm72h1cBbWLSMUVEu3t7bUT2RqBMppLdtVGH3VqKhppz96YE6yQSS4GKKkB6nHCRXmdiukHkw3Puy5Zd5b2rvaosRG

const hash = [
  "-----BEGIN TEZOS SIGNED MESSAGE-----",
  signedMessage,
  "-----BEGIN SIGNATURE-----",
  publicKey,
  signature,
  "-----END TEZOS SIGNED MESSAGE-----",
].join("\n");

const url =
  "https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=tezos_mainnet&bdc=XTZ&addr=" +
  address +
  "&code=" +
  code +
  "&hash=" +
  encodeURIComponent(hash);
```

### NodeJS, Taquito

```js
const { InMemorySigner } = require("@taquito/signer");

/* Example for seed bamboo feed assist glove soda merry medal vanish almost solid bean loop */
const code = "1234";
const signedMessage = "Tezos Signed Message: MtPelerin-" + code;

const bytes = Buffer.from(signedMessage, "utf8");
const payload =
  "0501" + bytes.length.toString(16).padStart(8, "0") + bytes.toString("hex");
// payload should be 05010000002454657a6f73205369676e6564204d6573736167653a204d7450656c6572696e2d31323334

InMemorySigner.fromMnemonic({
  mnemonic:
    "bamboo feed assist glove soda merry medal vanish almost solid bean loop",
  derivationPath: "m/44'/1729'/0'/0'",
  curve: "ed25519",
})
  .then(async (signer) => {
    const address = await signer.publicKeyHash();
    // address should be tz1dnCbNYHDoxmHss9kEQVjWQj6GvYX4gYp5
    const publicKey = await signer.publicKey();
    // publicKey should be edpkus9ckWtwxNi7NqZqGT2bo1VGxyRkCvaV5zkms7rkeWUV532PhU

    const { prefixSig } = await signer.sign(payload); // no watermark
    // prefixSig should be edsigtzYwqm72h1cBbWLSMUVEu3t7bUT2RqBMppLdtVGH3VqKhppz96YE6yQSS4GKKkB6nHCRXmdiukHkw3Puy5Zd5b2rvaosRG

    const hash = [
      "-----BEGIN TEZOS SIGNED MESSAGE-----",
      signedMessage,
      "-----BEGIN SIGNATURE-----",
      publicKey,
      prefixSig,
      "-----END TEZOS SIGNED MESSAGE-----",
    ].join("\n");

    console.log(encodeURIComponent(hash));
  })
  .catch(console.log);
```

### Ready to use URL

```
https://widget.mtpelerin.com/?_ctkn=954139b2-ef3e-4914-82ea-33192d3f43d3&net=tezos_mainnet&bdc=XTZ&addr=tz1dnCbNYHDoxmHss9kEQVjWQj6GvYX4gYp5&code=1234&hash=-----BEGIN%20TEZOS%20SIGNED%20MESSAGE-----%0ATezos%20Signed%20Message%3A%20MtPelerin-1234%0A-----BEGIN%20SIGNATURE-----%0Aedpkus9ckWtwxNi7NqZqGT2bo1VGxyRkCvaV5zkms7rkeWUV532PhU%0AedsigtzYwqm72h1cBbWLSMUVEu3t7bUT2RqBMppLdtVGH3VqKhppz96YE6yQSS4GKKkB6nHCRXmdiukHkw3Puy5Zd5b2rvaosRG%0A-----END%20TEZOS%20SIGNED%20MESSAGE-----
```

### Supported Tezos addresses

* `tz1` (Ed25519), `tz2` (secp256k1) and `tz3` (P-256) are supported.
* `KT1` originated accounts (smart contracts) and `tz4` (BLS) cannot be validated by signature.
* The address is case sensitive. Pass it exactly as the wallet returns it, never lowercased.

## Lightning

Lightning accounts cannot be validated through this URL mechanism, because the proof is produced by the node key while the account holds a Lightning address. They are validated inside the widget, or by paying a 1 sat invoice.
