Skip to main content

SDK API Reference

Every public function is exported at the top level:

import * as Specter from '@specterpq/sdk';

This page is reference material. For a guided walkthrough, start with the quickstart. For patterns, see integration.

Note

Two layers exist on purpose. The high-level flow functions (createStealthPayment, scanAnnouncement) cover most apps. The low-level functions (encapsulate, decapsulate, deriveStealthKeys) are there when you need to control each step yourself.

Initialization

initSpecterSdk(opts?)

Loads and caches the WebAssembly module. Call it once before any other crypto. Safe to call repeatedly.

await initSpecterSdk();

// Browser override when you host the WASM yourself:
await initSpecterSdk({ wasmUrl: 'https://cdn.example.com/specter_wasm_bg.wasm' });

Key generation

generateKeysLocal()

Generates one ML-KEM-768 keypair. The public key is safe to log. The secret key is redacted from JSON and console output.

const kp = generateKeysLocal();
kp.publicKey; // safe to share
kp.secretKey; // secret-bearing, redacted

generateSpecterKeys()

Generates a recipient identity: a spending keypair and a viewing keypair.

const keys = generateSpecterKeys();
keys.spending.publicKey; // controls funds
keys.viewing.publicKey; // detects payments

specterKeysViewingPk(keys)

Reads the viewing public key from a full identity object.

const viewingPk = specterKeysViewingPk(keys);

Meta-address

metaAddressFromPublicKeys(spendingPk, viewingPk, metadata?)

Builds the canonical recipient meta-address from the two public keys, plus optional metadata.

const meta = metaAddressFromPublicKeys(
spending.publicKey,
viewing.publicKey,
{ description: 'Alice', avatar: 'ipfs://Qm...', createdAt: 1700000000 },
);

meta.hex; // publishable string
meta.bytes.length; // 2369
meta.address.version; // 1

parseMetaAddress(input)

Parses a serialized meta-address from its hex string or raw bytes.

const parsed = parseMetaAddress(meta.hex);
parsed.address.spendingPk;
parsed.address.viewingPk;

Key encapsulation (KEM)

These are the post-quantum core. The sender encapsulates to the viewing public key; the recipient decapsulates with the viewing secret key. Both sides end up with the same 32-byte shared secret.

encapsulate(publicKey)

Sender-side operation against the recipient viewing public key.

const enc = encapsulate(recipient.viewing.publicKey);
enc.ciphertext; // announce publicly
enc.sharedSecret; // secret-bearing, redacted

decapsulate(ciphertext, secretKey)

Recipient-side operation against the viewing secret key. Returns the same shared secret.

const sharedSecret = decapsulate(enc.ciphertext, recipient.viewing.secretKey);

View tags

The view tag is a one-byte filter, not encryption. It lets a recipient skip almost every announcement that is not theirs before doing real work. See view tags and scanning for the why.

computeViewTag(sharedSecret)

Returns the one-byte view tag as a number from 0 to 255.

const tag = computeViewTag(sharedSecret);

verifyViewTag(sharedSecret, expectedTag)

Returns true if the tag matches.

if (verifyViewTag(sharedSecret, incomingTag)) {
// candidate match, continue to full derivation
}

Stealth derivation

deriveStealthAddress(spendingPk, sharedSecret)

Derives the stealth Ethereum address, 0x plus 20 bytes.

const ethAddress = deriveStealthAddress(recipient.spending.publicKey, sharedSecret);

deriveStealthSuiAddress(spendingPk, sharedSecret)

Derives the stealth Sui address, 0x plus 32 bytes.

const suiAddress = deriveStealthSuiAddress(recipient.spending.publicKey, sharedSecret);

deriveStealthKeys(spendingPk, sharedSecret)

Derives the full spendable key material for wallet import.

const keys = deriveStealthKeys(recipient.spending.publicKey, sharedSecret);
keys.ethAddress;
keys.suiAddress;
keys.publicKey; // secp256k1 uncompressed public key
keys.ethPrivateKey; // secret-bearing, redacted

High-level flow

These two functions wrap the steps above. Reach for them first.

createStealthPayment(metaAddress)

Sender helper. Parses the meta-address, encapsulates, derives the Ethereum and Sui stealth addresses, and computes the view tag.

const payment = createStealthPayment(meta.hex);
// { ephemeralCiphertext, viewTag, ethAddress, suiAddress }

scanAnnouncement(announcement, viewingKeys, spendingPublicKey)

Recipient helper for a single announcement.

const result = scanAnnouncement(
{ ephemeralCiphertext: payment.ephemeralCiphertext, viewTag: payment.viewTag },
recipient.viewing,
recipient.spending.publicKey,
);

if (result.isMatch) {
result.stealthKeys.ethAddress;
} else {
result.reason; // 'view_tag_mismatch' | 'address_mismatch'
}

scanAnnouncements(announcements, viewingKeys, spendingPublicKey)

Batch version. Returns an array of results.

const results = scanAnnouncements(batch, recipient.viewing, recipient.spending.publicKey);
const matches = results.filter((r) => r.isMatch);

Sizes and constants

Import these for runtime checks and schema alignment instead of hardcoding numbers. The values match ML-KEM-768 as standardized in FIPS 203.

ConstantBytesWhat it measures
KYBER_PUBLIC_KEY_SIZE1184ML-KEM-768 public key
KYBER_SECRET_KEY_SIZE2400ML-KEM-768 secret key
KYBER_CIPHERTEXT_SIZE1088Encapsulation ciphertext
KYBER_SHARED_SECRET_SIZE32Shared secret
META_ADDRESS_SIZE2369Serialized meta-address
VIEW_TAG_SIZE1View tag
ETH_ADDRESS_SIZE20Stealth Ethereum address
SUI_ADDRESS_SIZE32Stealth Sui address
STEALTH_SECP256K1_PUBLIC_SIZE65Uncompressed secp256k1 public key
STEALTH_ETH_PRIVATE_KEY_SIZE32Recovered Ethereum private key

PROTOCOL_VERSION carries the current protocol version (1).

import {
KYBER_PUBLIC_KEY_SIZE,
KYBER_CIPHERTEXT_SIZE,
META_ADDRESS_SIZE,
VIEW_TAG_SIZE,
PROTOCOL_VERSION,
} from '@specterpq/sdk';

Errors

Every error the SDK throws is a SpecterSdkError with a code, a category, a recoverable flag, and a message.

import { SpecterSdkError, encapsulate } from '@specterpq/sdk';

try {
encapsulate('0xdeadbeef' as never);
} catch (err) {
if (err instanceof SpecterSdkError) {
console.error(err.code, err.category, err.recoverable, err.message);
} else {
throw err;
}
}
CodeMeaning
NOT_INITIALIZEDCrypto called before initSpecterSdk()
INVALID_KEY_SIZEKey length does not match the expected size
INVALID_CIPHERTEXT_SIZECiphertext length is wrong
INVALID_SHARED_SECRET_SIZEShared secret length is wrong
INVALID_HEXInput is not valid hex
INVALID_META_ADDRESSMeta-address failed to parse
INVALID_METADATA_JSONMetadata is not valid JSON
INVALID_VIEW_TAGView tag is out of range
INVALID_API_RESPONSEA remote response failed validation
HTTP_ERRORThe trusted API returned a non-success status
ENCAPSULATION_FAILEDEncapsulation failed inside WASM
DECAPSULATION_FAILEDDecapsulation failed inside WASM
STEALTH_DERIVATION_FAILEDStealth derivation failed
WASM_LOAD_FAILEDThe WebAssembly module could not load
INTERNAL_ERRORUnexpected internal failure

Type notes

  • Cryptographic strings use branded hex aliases such as KyberPublicKeyHex.
  • Most low-level functions accept either branded hex or a Uint8Array.
  • Prefer the high-level functions for app integration unless you need step-by-step control.

Trusted HTTP client

The SDK is local first. The functions below only run when you build a client against an API you trust. They are documented in integration patterns, and their trust implications are covered in the security model.

import { createSpecterApiClient } from '@specterpq/sdk';

const api = createSpecterApiClient({
baseUrl: 'https://api.example.com',
headers: { authorization: `Bearer ${token}` },
});

Available methods: generateKeysRemote(), createStealthPaymentRemote(metaAddress), publishAnnouncement(input), and scanRemote(input).

Warning

generateKeysRemote() and scanRemote() can send secret material to the server. Use them only against infrastructure you control, over TLS, behind your own authorization. For wallets, prefer generateSpecterKeys() and scanAnnouncement(), which never leave the device.