@majikah/majik-key 0.2.12 → 0.2.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,44 +1,135 @@
1
1
  import { MnemonicLanguage } from "./crypto/wordlist";
2
+ /** ISO 8601 timestamp string, e.g. `"2026-07-11T00:00:00.000Z"`. */
2
3
  export type ISODateString = string;
3
4
  export type MajikMessageAccountID = string;
4
5
  export type MajikMessagePublicKey = string;
5
6
  export type MajikMessageChatID = string;
7
+ /** Base64-encoded public key material. Safe to store, log, or transmit. */
8
+ export type MajikKeyAddress = string;
9
+ /** Base64-encoded SHA-256 digest of a MajikKey's X25519 public key. Doubles as the account `id`. */
10
+ export type MajikKeyFingerprint = string;
11
+ /**
12
+ * Safe, serializable snapshot of a MajikKey — what `toJSON()` / `toString()` produce.
13
+ *
14
+ * Every `encrypted*` field is an AES-256-GCM ciphertext (IV + ciphertext,
15
+ * base64-encoded) protected by a passphrase-derived Argon2id key (or legacy
16
+ * PBKDF2, see `kdfVersion`). None of these fields ever contain raw private
17
+ * key material — this shape is safe to persist in a database, localStorage,
18
+ * or anywhere else at rest.
19
+ *
20
+ * Load one of these back into a live instance with `MajikKey.fromJSON()`.
21
+ */
6
22
  export interface MajikKeyJSON {
23
+ /** Account identifier. Equal to `fingerprint` for accounts created by this library. */
7
24
  id: string;
25
+ /** Human-readable, user-editable account name. */
8
26
  label: string;
9
- publicKey: string;
10
- fingerprint: string;
27
+ /** X25519 public key, base64. */
28
+ publicKey: MajikKeyAddress;
29
+ /** SHA-256 fingerprint of `publicKey`. Stable identity anchor for the account. */
30
+ fingerprint: MajikKeyFingerprint;
31
+ /** AES-256-GCM-encrypted X25519 private key (IV + ciphertext), base64. Requires the passphrase to decrypt. */
11
32
  encryptedPrivateKey: string;
33
+ /** Random salt used to derive the passphrase-based encryption key. Shared across all key types on this account. */
12
34
  salt: string;
35
+ /**
36
+ * Encrypted, mnemonic-verification blob (base64 JSON). Decryptable only with
37
+ * the original mnemonic — used internally to verify a supplied mnemonic
38
+ * before `importFromMnemonicBackup()` re-derives the full identity. Not a
39
+ * general-purpose backup of the private key.
40
+ */
13
41
  backup: string;
42
+ /** Account creation time, ISO 8601. */
14
43
  timestamp: string;
44
+ /** KDF used for every `encrypted*` field on this account: `1` = legacy PBKDF2 (read-only), `2` = Argon2id (current). Defaults to `1` if omitted. */
15
45
  kdfVersion?: number;
46
+ /** ML-KEM-768 (FIPS-203) public key, base64. Post-quantum key encapsulation. */
16
47
  mlKemPublicKey?: string;
48
+ /** AES-256-GCM-encrypted ML-KEM-768 secret key, base64. */
17
49
  encryptedMlKemSecretKey?: string;
50
+ /** Ed25519 public key, base64. Classical signing — same keypair the X25519 identity key is converted from. */
18
51
  edPublicKey?: string;
52
+ /** AES-256-GCM-encrypted Ed25519 secret key, base64. */
19
53
  encryptedEdSecretKey?: string;
54
+ /** ML-DSA-87 (FIPS-204) public key, base64. Post-quantum signing. */
20
55
  mlDsaPublicKey?: string;
56
+ /** AES-256-GCM-encrypted ML-DSA-87 secret key, base64. */
21
57
  encryptedMlDsaSecretKey?: string;
58
+ /** @experimental secp256k1 Bitcoin public key, base64. Domain-separated BIP-32/84 derivation by default — see `MajikKeyBitcoinNamespace`. */
59
+ btcPublicKey?: string;
60
+ /** @experimental AES-256-GCM-encrypted Bitcoin private key, base64. */
61
+ encryptedBtcSecretKey?: string;
62
+ /** BIP-39 wordlist language the original mnemonic was generated/validated against. Defaults to `"en"`. */
22
63
  mnemonicLanguage?: MnemonicLanguage;
23
64
  }
65
+ /**
66
+ * ⚠️ DANGEROUS. Every field below is a *raw, unencrypted* private key,
67
+ * base64-encoded — no passphrase, no KDF, no AES-GCM. Anyone with this
68
+ * object has full control of the account.
69
+ *
70
+ * Intended for one narrow use case: injecting a pre-unlocked signing key
71
+ * into a server process at boot (e.g. loaded from a secrets manager). Never
72
+ * log, store in a database, send over the network, or write to disk outside
73
+ * of a secrets manager.
74
+ *
75
+ * Produced by `toDangerousJSON()`, consumed by `MajikKey.fromDangerousJSON()`.
76
+ */
24
77
  export interface MajikKeyDangerousJSON extends MajikKeyJSON {
78
+ /** ⚠️ Raw X25519 private key, base64. Unencrypted. */
25
79
  privateKeyBase64: string;
80
+ /** ⚠️ Raw ML-KEM-768 secret key, base64. Unencrypted. */
26
81
  mlKemSecretKeyBase64: string;
82
+ /** ⚠️ Raw Ed25519 secret key, base64. Unencrypted. */
27
83
  edSecretKeyBase64: string;
84
+ /** ⚠️ Raw ML-DSA-87 secret key, base64. Unencrypted. */
28
85
  mlDsaSecretKeyBase64: string;
86
+ /** @experimental ⚠️ Raw Bitcoin private key, base64. Unencrypted. */
87
+ btcSecretKeyBase64?: string;
29
88
  }
89
+ /**
90
+ * Lightweight, non-secret summary of a MajikKey — useful for account
91
+ * pickers, dashboards, or anywhere you want to display account state
92
+ * without touching encrypted key material. Contains no key bytes at all
93
+ * (not even encrypted ones), so it's cheaper to pass around than `MajikKeyJSON`.
94
+ *
95
+ * Get one via the `metadata` getter on a live `MajikKey` instance.
96
+ */
30
97
  export interface MajikKeyMetadata {
31
98
  id: string;
32
- fingerprint: string;
99
+ fingerprint: MajikKeyFingerprint;
33
100
  label: string;
34
101
  timestamp: Date;
102
+ /** `true` if private key material is currently purged from memory (i.e. `lock()` was called, or it hasn't been `unlock()`ed yet). */
35
103
  isLocked: boolean;
104
+ /** `1` = legacy PBKDF2, `2` = Argon2id. See `MajikKeyJSON.kdfVersion`. */
36
105
  kdfVersion: number;
106
+ /** `true` if this account has ML-KEM-768 keys (i.e. is post-quantum-encryption capable). `false` means it's a legacy account pending migration. */
37
107
  hasMlKem: boolean;
108
+ /** @experimental Presence flags for optional Web3 key material. */
109
+ web3: {
110
+ /** @experimental `true` if this account has a stored Bitcoin keypair. */
111
+ hasBitcoin?: boolean;
112
+ /** @experimental `true` if this account can derive a Solana keypair (i.e. has an Ed25519 signing key and is unlocked). */
113
+ hasSolana?: boolean;
114
+ };
38
115
  mnemonicLanguage?: MnemonicLanguage;
39
116
  }
117
+ /**
118
+ * Portable seed export — the format behind `toMnemonicJSON()` /
119
+ * `MajikKey.fromMnemonicJSON()`.
120
+ *
121
+ * ⚠️ Unlike `MajikKeyJSON`, this is **not an encrypted-at-rest format**.
122
+ * `seed` is the raw mnemonic, split into words, in plaintext. If a
123
+ * passphrase is included, it's plaintext too. Treat any `MnemonicJSON`
124
+ * exactly like the mnemonic itself — fine for a one-time, protected
125
+ * transport (e.g. into an encrypted file you control), not for long-term
126
+ * storage. Use `MajikKeyJSON` / `toJSON()` for anything persisted at rest.
127
+ */
40
128
  export interface MnemonicJSON {
129
+ /** Raw mnemonic, split into individual words. ⚠️ Plaintext — this *is* the recovery phrase. */
41
130
  seed: string[];
131
+ /** The account's encrypted backup blob (`MajikKeyJSON.backup`), carried along so this object alone is enough to call `importFromMnemonicBackup()`. */
42
132
  id: string;
133
+ /** Optional passphrase, carried in plaintext for convenience during export/import. ⚠️ Not encrypted. */
43
134
  phrase?: string;
44
135
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * bitcoin.ts
3
+ *
4
+ * ⚠️ EXPERIMENTAL — Bitcoin keypair utilities for MajikKey.
5
+ * This module's API may change or be removed without notice in a minor version.
6
+ *
7
+ * Design:
8
+ * - Real BIP-32/BIP-84 HD derivation directly off the raw 64-byte BIP-39
9
+ * seed — NOT a hash-based domain separation like Solana. This matters:
10
+ * BIP-32's tree structure lets us get privacy AND portability from the
11
+ * exact same standard, auditable derivation, just by choosing the path:
12
+ *
13
+ * MAJIK_BITCOIN_DOMAIN_PATH (default) — effectively private to Majik;
14
+ * not the path any generic wallet would derive by default.
15
+ * MAJIK_BITCOIN_STANDARD_PATH (opt-in) — the REAL BIP-84 mainnet path;
16
+ * recoverable in any standard wallet using nothing but the mnemonic.
17
+ *
18
+ * - `privateKey` is the raw 32-byte secp256k1 scalar. `toWIF()` encodes it
19
+ * into Wallet Import Format — the universal paste-in-import string every
20
+ * Bitcoin wallet accepts (base58check, versioned, deterministic).
21
+ * - `@scure/btc-signer` is NOT a hard dependency. It is lazily `import()`-ed
22
+ * only for bech32 address encoding / PSBT construction. If it isn't
23
+ * installed, we throw a clear, actionable MajikKeyError instead of
24
+ * failing module load.
25
+ */
26
+ export interface BitcoinKeypairMaterial {
27
+ /** 32-byte secp256k1 private key. */
28
+ privateKey: Uint8Array;
29
+ /** 33-byte compressed secp256k1 public key. */
30
+ publicKey: Uint8Array;
31
+ }
32
+ export interface BitcoinDerivationOptions {
33
+ /**
34
+ * If true, derive the REAL BIP-84 mainnet path (SLIP-44 coin type 0) —
35
+ * the address any standard wallet would show for this mnemonic.
36
+ * Defaults to false (Majik's domain-separated path).
37
+ */
38
+ standard?: boolean;
39
+ /** Explicit derivation path — overrides `standard` if provided. */
40
+ path?: string;
41
+ }
42
+ /**
43
+ * Derive a Bitcoin keypair via standard BIP-32/BIP-84 from the raw 64-byte
44
+ * BIP-39 seed. Call this once at account creation/import time (mirrors
45
+ * ML-KEM/Ed25519/ML-DSA derivation) — the seed itself is never stored, only
46
+ * the resulting key, encrypted at rest like the others.
47
+ */
48
+ export declare function deriveBitcoinKeypairFromSeed(seed: Uint8Array, options?: BitcoinDerivationOptions): BitcoinKeypairMaterial;
49
+ /**
50
+ * Re-derive the public key from a raw private key. Used when unlocking —
51
+ * we only encrypt/store the private key, so the public key is recomputed
52
+ * on unlock rather than stored redundantly encrypted.
53
+ */
54
+ export declare function bitcoinPublicKeyFromPrivateKey(privateKey: Uint8Array): Uint8Array;
55
+ /** Sign a 32-byte message hash (already hashed — e.g. a Bitcoin sighash). */
56
+ export declare function signWithBitcoinMaterial(material: BitcoinKeypairMaterial, messageHash: Uint8Array, scheme?: "ecdsa" | "schnorr"): Uint8Array;
57
+ /**
58
+ * Encode a private key as WIF — the universal paste-in-import string every
59
+ * Bitcoin wallet accepts. Deterministic: same private key → same WIF, always.
60
+ */
61
+ export declare function toWIF(material: BitcoinKeypairMaterial, options?: {
62
+ compressed?: boolean;
63
+ }): string;
64
+ type BtcSignerModule = typeof import("@scure/btc-signer");
65
+ export declare function loadBtcSigner(): Promise<BtcSignerModule>;
66
+ /**
67
+ * Native SegWit (bech32, "bc1...") mainnet address for this material's
68
+ * public key. Requires @scure/btc-signer — see loadBtcSigner().
69
+ */
70
+ export declare function toBitcoinAddress(material: BitcoinKeypairMaterial): Promise<string>;
71
+ export {};
@@ -0,0 +1,126 @@
1
+ /**
2
+ * bitcoin.ts
3
+ *
4
+ * ⚠️ EXPERIMENTAL — Bitcoin keypair utilities for MajikKey.
5
+ * This module's API may change or be removed without notice in a minor version.
6
+ *
7
+ * Design:
8
+ * - Real BIP-32/BIP-84 HD derivation directly off the raw 64-byte BIP-39
9
+ * seed — NOT a hash-based domain separation like Solana. This matters:
10
+ * BIP-32's tree structure lets us get privacy AND portability from the
11
+ * exact same standard, auditable derivation, just by choosing the path:
12
+ *
13
+ * MAJIK_BITCOIN_DOMAIN_PATH (default) — effectively private to Majik;
14
+ * not the path any generic wallet would derive by default.
15
+ * MAJIK_BITCOIN_STANDARD_PATH (opt-in) — the REAL BIP-84 mainnet path;
16
+ * recoverable in any standard wallet using nothing but the mnemonic.
17
+ *
18
+ * - `privateKey` is the raw 32-byte secp256k1 scalar. `toWIF()` encodes it
19
+ * into Wallet Import Format — the universal paste-in-import string every
20
+ * Bitcoin wallet accepts (base58check, versioned, deterministic).
21
+ * - `@scure/btc-signer` is NOT a hard dependency. It is lazily `import()`-ed
22
+ * only for bech32 address encoding / PSBT construction. If it isn't
23
+ * installed, we throw a clear, actionable MajikKeyError instead of
24
+ * failing module load.
25
+ */
26
+ import { HDKey } from "@scure/bip32";
27
+ import { schnorr, secp256k1 } from "@noble/curves/secp256k1.js";
28
+ import { MAJIK_BITCOIN_STANDARD_PATH, MAJIK_BITCOIN_DOMAIN_PATH, } from "./constants";
29
+ import { hash } from "@stablelib/sha256";
30
+ import { base58Encode } from "../utils";
31
+ import { MajikKeyError } from "../../error";
32
+ import { randomBytes } from "@noble/hashes/utils.js";
33
+ // ─── Derivation ─────────────────────────────────────────────────────────────
34
+ /**
35
+ * Derive a Bitcoin keypair via standard BIP-32/BIP-84 from the raw 64-byte
36
+ * BIP-39 seed. Call this once at account creation/import time (mirrors
37
+ * ML-KEM/Ed25519/ML-DSA derivation) — the seed itself is never stored, only
38
+ * the resulting key, encrypted at rest like the others.
39
+ */
40
+ export function deriveBitcoinKeypairFromSeed(seed, options) {
41
+ const path = options?.path ??
42
+ (options?.standard
43
+ ? MAJIK_BITCOIN_STANDARD_PATH
44
+ : MAJIK_BITCOIN_DOMAIN_PATH);
45
+ const child = HDKey.fromMasterSeed(seed).derive(path);
46
+ if (!child.privateKey || !child.publicKey) {
47
+ throw new MajikKeyError("Failed to derive Bitcoin keypair from seed");
48
+ }
49
+ return {
50
+ privateKey: child.privateKey,
51
+ publicKey: child.publicKey,
52
+ };
53
+ }
54
+ /**
55
+ * Re-derive the public key from a raw private key. Used when unlocking —
56
+ * we only encrypt/store the private key, so the public key is recomputed
57
+ * on unlock rather than stored redundantly encrypted.
58
+ */
59
+ export function bitcoinPublicKeyFromPrivateKey(privateKey) {
60
+ return secp256k1.getPublicKey(privateKey, true); // compressed
61
+ }
62
+ /** Sign a 32-byte message hash (already hashed — e.g. a Bitcoin sighash). */
63
+ export function signWithBitcoinMaterial(material, messageHash, scheme = "ecdsa") {
64
+ if (scheme === "schnorr") {
65
+ return schnorr.sign(messageHash, material.privateKey, randomBytes(32));
66
+ }
67
+ const signature = secp256k1.sign(messageHash, material.privateKey, {
68
+ prehash: false,
69
+ lowS: true, // Note: lowS is technically default in v2 for secp256k1, but it's good practice to be explicit!
70
+ });
71
+ return signature;
72
+ }
73
+ // ─── WIF export (Wallet Import Format) — no external lib needed ────────────
74
+ const WIF_VERSION_MAINNET = 0x80;
75
+ function doubleSha256(data) {
76
+ return hash(hash(data));
77
+ }
78
+ function base58checkEncode(payload) {
79
+ const checksum = doubleSha256(payload).slice(0, 4);
80
+ const full = new Uint8Array(payload.length + 4);
81
+ full.set(payload, 0);
82
+ full.set(checksum, payload.length);
83
+ return base58Encode(full);
84
+ }
85
+ /**
86
+ * Encode a private key as WIF — the universal paste-in-import string every
87
+ * Bitcoin wallet accepts. Deterministic: same private key → same WIF, always.
88
+ */
89
+ export function toWIF(material, options) {
90
+ const compressed = options?.compressed ?? true;
91
+ const payload = new Uint8Array(compressed ? 34 : 33);
92
+ payload[0] = WIF_VERSION_MAINNET;
93
+ payload.set(material.privateKey, 1);
94
+ if (compressed)
95
+ payload[33] = 0x01;
96
+ return base58checkEncode(payload);
97
+ }
98
+ let _btcModule = null;
99
+ export async function loadBtcSigner() {
100
+ if (_btcModule)
101
+ return _btcModule;
102
+ try {
103
+ _btcModule = (await import(
104
+ /* webpackIgnore: true */
105
+ /* @vite-ignore */
106
+ "@scure/btc-signer"));
107
+ return _btcModule;
108
+ }
109
+ catch (err) {
110
+ throw new MajikKeyError("@scure/btc-signer is required for this operation but is not installed. " +
111
+ "Install it in your project with `npm install @scure/btc-signer` " +
112
+ "(or the yarn/pnpm equivalent) and try again.", err);
113
+ }
114
+ }
115
+ /**
116
+ * Native SegWit (bech32, "bc1...") mainnet address for this material's
117
+ * public key. Requires @scure/btc-signer — see loadBtcSigner().
118
+ */
119
+ export async function toBitcoinAddress(material) {
120
+ const btc = await loadBtcSigner();
121
+ const p2wpkh = btc.p2wpkh(material.publicKey);
122
+ if (!p2wpkh.address) {
123
+ throw new MajikKeyError("Failed to derive Bitcoin address");
124
+ }
125
+ return p2wpkh.address;
126
+ }
@@ -0,0 +1,2 @@
1
+ export declare const MAJIK_BITCOIN_STANDARD_PATH = "m/84'/0'/0'/0/0";
2
+ export declare const MAJIK_BITCOIN_DOMAIN_PATH = "m/84'/1989'/0'/0/0";
@@ -0,0 +1,7 @@
1
+ // SLIP-44 coin type 0 = Bitcoin mainnet — the path any standard wallet
2
+ // (Electrum, Sparrow, hardware wallets) derives by default from this mnemonic.
3
+ export const MAJIK_BITCOIN_STANDARD_PATH = "m/84'/0'/0'/0/0";
4
+ // Unregistered/private coin-type index — domain-separates Majik's default
5
+ // Bitcoin key from a user's "real" BTC wallet, while remaining 100% standard
6
+ // BIP-32 math (just a different branch of the same tree).
7
+ export const MAJIK_BITCOIN_DOMAIN_PATH = "m/84'/1989'/0'/0/0";
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @experimental By default this is Majik's DOMAIN-SEPARATED Bitcoin key
3
+ * (derived via `MAJIK_BITCOIN_DOMAIN_PATH`) — deterministic and fully
4
+ * standard BIP-32, but not the path a generic wallet would derive by
5
+ * default, so it stays effectively private to Majik. Use
6
+ * `MajikKey.getBitcoinKeypairMaterial({ standard: true })` /
7
+ * `getBitcoinWIF({ standard: true })` for the REAL BIP-84 mainnet key —
8
+ * recoverable in any standard wallet from the same mnemonic alone.
9
+ */
10
+ export interface MajikKeyBitcoinNamespace {
11
+ /** 33-byte compressed secp256k1 public key. */
12
+ readonly publicKey: Uint8Array;
13
+ /** 32-byte secp256k1 private key. Handle with the same care as any private key. */
14
+ readonly privateKey: Uint8Array;
15
+ /** Native SegWit (bech32) address. Lazily loads @scure/btc-signer — throws if not installed. */
16
+ getBitcoinAddress(): Promise<string>;
17
+ /** WIF string — pastes directly into any standard Bitcoin wallet. */
18
+ getWIF(options?: {
19
+ compressed?: boolean;
20
+ }): string;
21
+ /** Sign a 32-byte message hash. ECDSA (default) or Schnorr. */
22
+ sign(messageHash: Uint8Array, scheme?: "ecdsa" | "schnorr"): Uint8Array;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export * from "./bitcoin/bitcoin";
2
+ export * from "./solana/solana";
3
+ export type * from "./types";
4
+ export type * from "./bitcoin/types";
5
+ export type * from "./solana/types";
@@ -0,0 +1,2 @@
1
+ export * from "./bitcoin/bitcoin";
2
+ export * from "./solana/solana";
@@ -49,7 +49,6 @@ export declare function deriveSolanaKeypairFromEdSecretKey(edSecretKey: Uint8Arr
49
49
  * identical key on both.
50
50
  */
51
51
  export declare function solanaMaterialFromEd25519SecretKey(edSecretKey: Uint8Array): SolanaKeypairMaterial;
52
- export declare function base58Encode(bytes: Uint8Array): string;
53
52
  /**
54
53
  * Solana address for a given Solana/Ed25519 public key — just its base58
55
54
  * encoding. Does NOT require @solana/web3.js.
@@ -28,7 +28,8 @@
28
28
  import * as ed25519 from "@stablelib/ed25519";
29
29
  import { hash } from "@stablelib/sha256";
30
30
  import { MAJIK_SOLANA_SEED } from "./constants";
31
- import { MajikKeyError } from "../error";
31
+ import { MajikKeyError } from "../../error";
32
+ import { base58Encode } from "../utils";
32
33
  const ED25519_SECRET_KEY_LENGTH = 64;
33
34
  const ED25519_SEED_LENGTH = 32;
34
35
  // ─── Derivation ─────────────────────────────────────────────────────────────
@@ -70,33 +71,6 @@ export function solanaMaterialFromEd25519SecretKey(edSecretKey) {
70
71
  secretKey: edSecretKey.slice(),
71
72
  };
72
73
  }
73
- // ─── Base58 (Solana address encoding) — no external dependency ─────────────
74
- const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
75
- export function base58Encode(bytes) {
76
- if (bytes.length === 0)
77
- return "";
78
- const digits = [0];
79
- for (let i = 0; i < bytes.length; i++) {
80
- let carry = bytes[i];
81
- for (let j = 0; j < digits.length; j++) {
82
- carry += digits[j] << 8;
83
- digits[j] = carry % 58;
84
- carry = (carry / 58) | 0;
85
- }
86
- while (carry > 0) {
87
- digits.push(carry % 58);
88
- carry = (carry / 58) | 0;
89
- }
90
- }
91
- let leadingZeros = 0;
92
- for (let i = 0; i < bytes.length && bytes[i] === 0; i++)
93
- leadingZeros++;
94
- let result = "1".repeat(leadingZeros);
95
- for (let i = digits.length - 1; i >= 0; i--) {
96
- result += BASE58_ALPHABET[digits[i]];
97
- }
98
- return result;
99
- }
100
74
  /**
101
75
  * Solana address for a given Solana/Ed25519 public key — just its base58
102
76
  * encoding. Does NOT require @solana/web3.js.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @experimental Web3 / blockchain integrations are experimental. This
3
+ * namespace's shape may change without a major version bump.
4
+ */
5
+ export interface MajikKeySolanaNamespace {
6
+ /** 32-byte Solana/Ed25519 public key. */
7
+ readonly publicKey: Uint8Array;
8
+ /** 64-byte nacl-format secret key. Handle with the same care as any private key. */
9
+ readonly secretKey: Uint8Array;
10
+ /** Base58 Solana address — does not require @solana/kit. */
11
+ readonly address: string;
12
+ /** Real @solana/kit Keypair. Lazily loads @solana/kit — throws if not installed. */
13
+ getSolanaKeypair(): Promise<any>;
14
+ /** Real @solana/kit PublicKey. Lazily loads @solana/kit — throws if not installed. */
15
+ getSolanaAddress(): Promise<any>;
16
+ /** Sign a message with this Solana keypair's Ed25519 key. No web3.js needed. */
17
+ sign(message: Uint8Array): Uint8Array;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,26 +1,7 @@
1
- /**
2
- * @experimental Web3 / blockchain integrations are experimental. This
3
- * namespace's shape may change without a major version bump.
4
- */
5
- export interface MajikKeySolanaNamespace {
6
- /** 32-byte Solana/Ed25519 public key. */
7
- readonly publicKey: Uint8Array;
8
- /** 64-byte nacl-format secret key. Handle with the same care as any private key. */
9
- readonly secretKey: Uint8Array;
10
- /** Base58 Solana address — does not require @solana/kit. */
11
- readonly address: string;
12
- /** Real @solana/kit Keypair. Lazily loads @solana/kit — throws if not installed. */
13
- getSolanaKeypair(): Promise<any>;
14
- /** Real @solana/kit PublicKey. Lazily loads @solana/kit — throws if not installed. */
15
- getSolanaAddress(): Promise<any>;
16
- /** Sign a message with this Solana keypair's Ed25519 key. No web3.js needed. */
17
- sign(message: Uint8Array): Uint8Array;
18
- }
19
- /** @experimental */
20
- export interface MajikKeyWeb3Namespace {
21
- readonly solana: MajikKeySolanaNamespace;
22
- }
1
+ import { MajikKeyBitcoinNamespace } from "./bitcoin/types";
2
+ import { MajikKeySolanaNamespace } from "./solana/types";
23
3
  /** @experimental */
24
4
  export interface MajikKeyWeb3Namespace {
25
5
  readonly solana: MajikKeySolanaNamespace;
6
+ readonly bitcoin?: MajikKeyBitcoinNamespace;
26
7
  }
@@ -0,0 +1 @@
1
+ export declare function base58Encode(bytes: Uint8Array): string;
@@ -0,0 +1,27 @@
1
+ // ─── Base58 (Solana address encoding) — no external dependency ─────────────
2
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
3
+ export function base58Encode(bytes) {
4
+ if (bytes.length === 0)
5
+ return "";
6
+ const digits = [0];
7
+ for (let i = 0; i < bytes.length; i++) {
8
+ let carry = bytes[i];
9
+ for (let j = 0; j < digits.length; j++) {
10
+ carry += digits[j] << 8;
11
+ digits[j] = carry % 58;
12
+ carry = (carry / 58) | 0;
13
+ }
14
+ while (carry > 0) {
15
+ digits.push(carry % 58);
16
+ carry = (carry / 58) | 0;
17
+ }
18
+ }
19
+ let leadingZeros = 0;
20
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++)
21
+ leadingZeros++;
22
+ let result = "1".repeat(leadingZeros);
23
+ for (let i = digits.length - 1; i >= 0; i--) {
24
+ result += BASE58_ALPHABET[digits[i]];
25
+ }
26
+ return result;
27
+ }
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from "./majik-key";
2
2
  export type * from "./core/types";
3
3
  export * from "./core/error";
4
4
  export * from "./core/validator";
5
+ export * from "./core/web3";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./majik-key";
2
2
  export * from "./core/error";
3
3
  export * from "./core/validator";
4
+ export * from "./core/web3";