0 / 9 Lessons

Lesson 10 of 1920 Min.+ 200 ExpBeing written · preview only

Create a Dash Identity

Create and verify a Dash Platform identity on testnet.

The funded tdash1… address can pay Platform fees, but it can't own the ticket marketplace's contracts or sign its documents. That work belongs to an identity: a persistent on-chain account with an ID, public keys, and a credit balance.

Creating one spends credits from the address you funded. Keep the .env file and mnemonic from Module 9 private; the only value you'll copy out of this script is the public identity ID.

Give each key one job

An identity can carry several public keys because signing a ticket listing shouldn't grant permission to replace the account's keys or withdraw its credits. Each key therefore has a purpose, which limits what it may authorize, and a security level, which tells clients how strongly to protect it.

The script will register five ECDSA secp256k1 keys:

KeyPurposeSecurity levelUsed for
0AUTHENTICATIONMASTERUpdating the identity's keys
1AUTHENTICATIONHIGHRoutine signed actions after authentication
2AUTHENTICATIONCRITICALActions that require fresh authentication
3TRANSFERCRITICALMoving credits out of the identity
4ENCRYPTIONMEDIUMEncrypting application data

MASTER is reserved for identity updates. CRITICAL keys should require authentication whenever they sign; a HIGH key may remain available during an authenticated session, while MEDIUM still requires access to the user's device. Platform also restricts the pairings: transfer keys use CRITICAL, and encryption keys use MEDIUM. Authentication keys may use any security level.

Exactly one MASTER authentication key is required. Keeping ordinary document work on key 1 or 2 means the root key doesn't have to sign every marketplace action.

Derive and register the identity

Create identity-register.mjs beside the files from Module 9:

identity-register.mjs
import { randomBytes } from 'node:crypto';
import { loadEnvFile } from 'node:process';
import {
  EvoSDK,
  Identity,
  Identifier,
  IdentityPublicKeyInCreation,
  IdentitySigner,
  KeyType,
  PlatformAddressSigner,
  PrivateKey,
  Purpose,
  SecurityLevel,
  wallet,
} from '@dashevo/evo-sdk';

loadEnvFile('.env');
const mnemonic = process.env.PLATFORM_MNEMONIC;
if (!mnemonic) throw new Error('PLATFORM_MNEMONIC is missing from .env');

const network = 'testnet';
const keySpecs = [
  [0, Purpose.AUTHENTICATION, SecurityLevel.MASTER],
  [1, Purpose.AUTHENTICATION, SecurityLevel.HIGH],
  [2, Purpose.AUTHENTICATION, SecurityLevel.CRITICAL],
  [3, Purpose.TRANSFER, SecurityLevel.CRITICAL],
  [4, Purpose.ENCRYPTION, SecurityLevel.MEDIUM],
];

function hexToBytes(hex) {
  return Uint8Array.from(hex.match(/.{2}/g), (byte) => parseInt(byte, 16));
}

// Recreate the funded address and its signer from Module 9.
const addressPath = (await wallet.derivationPathBip44Testnet(0, 0, 0)).path;
const addressKey = await wallet.deriveKeyFromSeedWithPath({
  mnemonic,
  path: addressPath,
  network,
});
const addressSigner = new PlatformAddressSigner();
const address = addressSigner.addKey(
  PrivateKey.fromWIF(addressKey.toObject().privateKeyWif),
).toBech32m(network);

// Derive five separate identity keys from the same mnemonic.
const identityPath = await wallet.derivationPathDip13Testnet(5);
const identity = new Identity(new Identifier(randomBytes(32)));
const identitySigner = new IdentitySigner();

for (const [keyId, purpose, securityLevel] of keySpecs) {
  const derived = await wallet.deriveKeyFromSeedWithPath({
    mnemonic,
    path: `${identityPath.path}/0'/0'/0'/${keyId}'`,
    network,
  });
  const { privateKeyWif, publicKey } = derived.toObject();

  identitySigner.addKeyFromWif(privateKeyWif);
  const key = new IdentityPublicKeyInCreation({
    keyId,
    purpose,
    securityLevel,
    keyType: KeyType.ECDSA_SECP256K1,
    data: hexToBytes(publicKey),
  });
  identity.addPublicKey(key.toIdentityPublicKey());
}

const sdk = EvoSDK.testnetTrusted();
await sdk.connect();

try {
  const result = await sdk.addresses.createIdentity({
    identity,
    inputs: [{ address, amount: 5000000n }],
    identitySigner,
    addressSigner,
  });

  console.log('Identity ID:', result.identity.id.toString());
} catch (error) {
  // SDK 4.1.1 can reject the returned proof after testnet has accepted the identity.
  const createdId = error.message?.match(/proof returned identity (\w+) but/)?.[1];
  if (!createdId) throw error;
  console.log('Identity ID:', createdId);
}

The BIP-44 path recreates the Platform address that pays for creation. The DIP-13 paths derive a different private key for each identity role; only their public halves enter the identity object. identitySigner proves control of those new keys, while addressSigner approves spending the address's credits.

The random identifier passed to new Identity(...) is only a local shell required by the SDK. Platform assigns the identity ID created by the state transition, which is why the script prints result.identity.id rather than the temporary value.

Decide where the credits go

The address pays two separate amounts. Platform charges an identity creation fee of 2,000,000 credits plus 6,500,000 for each key:

2,000,000 + (5 × 6,500,000) = 34,500,000 credits

The amount: 5000000n input assigns 5,000,000 credits to the new identity. The source address must hold that amount in addition to the creation cost and the transition's processing fee. Processing depends on current Platform fee parameters, so 34,500,000 + 5,000,000 is a floor, not a safe funding target. Keep extra testnet credits on the address; Platform charges the actual processing fee when it accepts the transition. Later name, contract, and document operations can spend the identity's starting balance.

If creation succeeds, the address balance falls by the fee and transferred amount. The identity begins with the transferred 5,000,000 credits; owning test DASH elsewhere doesn't increase that balance.

Run it once

node identity-register.mjs

Copy the complete Base58 value after Identity ID:. Don't rerun the script after it succeeds: every run derives the same keys but asks Platform to create another identity and charge another creation fee.

SDK 4.1.1 has a known proof-checking failure that can occur after testnet accepted the state transition. The catch block recognizes only that specific response and recovers the identity ID it contains; every other error still stops the script.

Inspect the public identity

Open the Dash Platform testnet explorer and search for the identity ID. Its page should show the five public keys and the remaining identity credit balance. None of the private keys or the mnemonic belongs there.

You can read the same public record in code with sdk.identities.fetch(identityId). Dash Academy performs that read independently and won't accept an ID that testnet can't find.

Verify your work

Paste the public result your script printed. Dash Academy will look it up independently on Dash Platform testnet.

This is public testnet data. Never paste a mnemonic or private key.