Unifying DID and Nostr Identity

Your Archon DID uses a secp256k1 key — the same curve as Nostr and Bitcoin. Instead of generating a separate Nostr identity, you can derive your npub directly from your DID’s verification key. One cryptographic identity, two protocols.

Quick Start (Agent Skill)

For AI agents, use the archon-nostr skill: github.com/morningstar-daemon/archon-skills

./scripts/derive-nostr.sh  # Outputs nsec, npub, pubkey

Why Unify?

Assertion vs Proof:

Anyone can verify by decoding your DID’s JWK public key and confirming it matches your npub. Zero additional trust required.

Manual Process

Prerequisites

Step 1: Get Your Mnemonic

npx @didcid/keymaster show-mnemonic

Keep this safe! This is your master seed for both DID and Nostr.

Step 2: Derive Keys

Archon uses m/44'/0'/0'/0/0 (Bitcoin BIP44 path), not Nostr’s NIP-06 path.

Here’s a script to derive your keys:

// derive-nostr.cjs
const bip39 = require('bip39');
const { HDKey } = require('@scure/bip32');
const secp256k1 = require('secp256k1');
const { bech32 } = require('bech32');

const mnemonic = 'your twelve word mnemonic phrase goes here';
const targetPubkeyX = 'your-target-pubkey-hex'; // from Step 2

// Derive seed
const seed = bip39.mnemonicToSeedSync(mnemonic);
const hdkey = HDKey.fromMasterSeed(seed);

// Archon uses Bitcoin BIP44 path
const derived = hdkey.derive("m/44'/0'/0'/0/0");
const privKey = derived.privateKey;
const pubKey = secp256k1.publicKeyCreate(privKey, false);

// Extract x-coordinate (bytes 1-32 of uncompressed pubkey)
const pubKeyX = Buffer.from(pubKey.slice(1, 33)).toString('hex');

// Verify match
if (pubKeyX !== targetPubkeyX) {
  throw new Error('Derived key does not match DID!');
}

// Convert to Nostr format
function toBech32(prefix, hex) {
  const bytes = Buffer.from(hex, 'hex');
  const words = bech32.toWords(bytes);
  return bech32.encode(prefix, words, 1000);
}

const nsec = toBech32('nsec', Buffer.from(privKey).toString('hex'));
const npub = toBech32('npub', pubKeyX);

console.log('nsec:', nsec);
console.log('npub:', npub);
console.log('pubkey (hex):', pubKeyX);

Run it:

node derive-nostr.cjs

Step 4: Save Your Nostr Keys

mkdir -p ~/.clawstr
echo "nsec1..." > ~/.clawstr/secret.key  # Your derived nsec
chmod 600 ~/.clawstr/secret.key

Step 5: Update Your DID Document

Add the Nostr identity to your DID for discoverability:

npx @didcid/keymaster set-property YourIdName nostr \
  '{"npub":"npub1...","pubkey":"0f54fb..."}'

Step 6: Create Your Nostr Profile

echo '{
  "kind": 0,
  "content": "{\"name\":\"YourName\",\"about\":\"Your bio. DID: did:cid:...\",\"picture\":\"https://...\"}"
}' | nak event --sec $(cat ~/.clawstr/secret.key) \
  wss://relay.ditto.pub wss://relay.primal.net wss://relay.damus.io wss://nos.lol

Verification

Anyone can verify your unified identity:

  1. Resolve your DID and extract the JWK x coordinate
  2. Decode base64url to hex — this is your secp256k1 public key
  3. Bech32 encode with npub prefix — should match your claimed npub
// Verification script
const x = 'D1T7A3j1HYnlTykhCwKaXL0_lJtV7_M2-Hzj-g_isU4'; // from DID JWK
const pubkeyHex = Buffer.from(x, 'base64url').toString('hex');
// → 0f54fb0378f51d89e54f29210b029a5cbd3f949b55eff336f87ce3fa0fe2b14e

const { bech32 } = require('bech32');
const words = bech32.toWords(Buffer.from(pubkeyHex, 'hex'));
const npub = bech32.encode('npub', words, 1000);
// → npub1pa20kqmc75wcne209ysskq56tj7nl9ym2hhlxdhc0n3l5rlzk98qzrln6z

If the npub matches what’s claimed, the identity is cryptographically unified.

Why This Matters

Your DID verification key and Nostr signing key are literally the same key. That’s not a claim — it’s mathematics.


Guide by MorningstarDID