QRDXDocs

Keys and signing

Generate ML-DSA-65 keys, derive addresses and account ids, and sign and verify messages.

Everything here runs in your browser with @noble/post-quantum, and matches QRDX Wallet and the node byte for byte.

Keys and addresses

JS
Create a post-quantum key and its address
output⌘/Ctrl + Enter to run
Edit the code and run it. It talks to testnet from your browser.

The derivations, so you can reproduce them in any language:

Address
address = "0xPQ" + checksum(hex(keccak256(publicKey)[0:32]))

checksum: h = hex(keccak256(utf8(lowercase hex body)))
          for each character i: a letter is upper-cased if int(h[i], 16) >= 8
Account id (the 20-byte ledger key)
accountId = "0x" + hex(keccak256(utf8("QRDX-ACCOUNT-ID-v1:pq:") ‖ addressBytes32)[12:32])
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'
import { keccak_256 } from '@noble/hashes/sha3.js'

const seed = crypto.getRandomValues(new Uint8Array(32))     // keep it secret
const { publicKey, secretKey } = ml_dsa65.keygen(seed)
const body = keccak_256(publicKey).slice(0, 32)              // the address bytes

Message signatures

qrdx_signPQMessage (the wallet) signs "\x19QRDX PQ Signed Message:\n" + byteLength(message) + message with ML-DSA-65 and returns { signature, publicKey, address }. ML-DSA does not recover a signer from a signature, so verify with the public key, then check that the key derives to the claimed address.

JS
Sign and verify a message
output⌘/Ctrl + Enter to run
Edit the code and run it. It talks to testnet from your browser.
Verifying a signature a wallet gave you
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js'

function prefixed(message) {
  const body = new TextEncoder().encode(message)
  const head = new TextEncoder().encode(`\x19QRDX PQ Signed Message:\n${body.length}`)
  return new Uint8Array([...head, ...body])
}

const ok = ml_dsa65.verify(hexToBytes(signature), prefixed(message), hexToBytes(publicKey))
// and: addressOf(publicKey) === claimedAddress (case-insensitive)

The byte length is UTF-8 bytes, not JavaScript string length: non-ASCII messages differ.

Transactions

Exchange transactions are signed over the exact bytes the node renders for them; see Exchange transactions. EVM transactions from a post-quantum account use the type 0x51 envelope (Transactions and fees).

Never paste a real key into a web page

The examples here make throwaway keys. A real account's seed comes from its recovery phrase and stays in the wallet.

On this page