@beclab/olaresid 0.1.4 → 0.1.5

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.
@@ -4,6 +4,12 @@ import { parseContractError } from '../utils/error-parser';
4
4
  import { TagContext } from './tag-context';
5
5
  import { TagTypeBuilder } from '../utils/tag-type-builder';
6
6
  import { normalizeToDomain } from '../utils/olares-id';
7
+ import {
8
+ base64ToUint8Array,
9
+ uint8ArrayToHex,
10
+ hexToUint8Array,
11
+ uint8ArrayToBase64
12
+ } from '../utils/crypto-utils';
7
13
 
8
14
  export interface TransactionResult<T = any> {
9
15
  // Basic transaction information
@@ -48,7 +54,11 @@ export {
48
54
  getDIDFromMnemonic,
49
55
  generateDIDKeyData,
50
56
  deriveDIDFromMnemonic,
51
- getEd25519JwkFromMnemonic
57
+ getEd25519JwkFromMnemonic,
58
+ base64ToUint8Array,
59
+ uint8ArrayToHex,
60
+ hexToUint8Array,
61
+ uint8ArrayToBase64
52
62
  } from '../utils/crypto-utils';
53
63
  export type { RSAPublicKeyData, DIDKeyData } from '../utils/crypto-utils';
54
64
 
@@ -180,7 +190,7 @@ export class DomainContext {
180
190
  * ```typescript
181
191
  * // For parent domain "parent.com", register subdomain "child"
182
192
  * const parentDomain = olaresId.domain('parent.com');
183
- * const mnemonic = generateMnemonic(12);
193
+ * const mnemonic = await generateMnemonic(12);
184
194
  *
185
195
  * const result = await parentDomain.registerSubdomain('child', mnemonic);
186
196
  * // This will register "child.parent.com"
@@ -266,7 +276,7 @@ export class DomainContext {
266
276
  *
267
277
  * @example
268
278
  * ```typescript
269
- * const mnemonic = generateMnemonic(12);
279
+ * const mnemonic = await generateMnemonic(12);
270
280
  * const result = await domain.transfer(mnemonic);
271
281
  * if (result.success) {
272
282
  * console.log('Domain transferred!');
@@ -846,18 +856,22 @@ export class DomainContext {
846
856
  const { Keypair } = await import('@solana/web3.js');
847
857
  const nacl = await import('tweetnacl');
848
858
  const { decodeUTF8 } = await import('tweetnacl-util');
859
+ const { base58btc } = await import('multiformats/bases/base58');
849
860
 
850
861
  // Parse Solana private key (support both base58 and base64)
851
862
  let solanaWallet: any;
852
863
  try {
853
864
  // Try base58 first (most common format for Phantom)
854
- const bs58 = await import('bs58');
855
- const secretKey = bs58.default.decode(solanaPrivateKey);
865
+ // Solana uses plain base58, but multiformats requires 'z' prefix for multibase
866
+ const base58Key = solanaPrivateKey.startsWith('z')
867
+ ? solanaPrivateKey
868
+ : 'z' + solanaPrivateKey;
869
+ const secretKey = base58btc.decode(base58Key);
856
870
  solanaWallet = Keypair.fromSecretKey(secretKey);
857
871
  } catch {
858
872
  // Try base64
859
873
  try {
860
- const secretKey = Buffer.from(solanaPrivateKey, 'base64');
874
+ const secretKey = base64ToUint8Array(solanaPrivateKey);
861
875
  solanaWallet = Keypair.fromSecretKey(secretKey);
862
876
  } catch {
863
877
  // Try as JSON array
@@ -868,9 +882,9 @@ export class DomainContext {
868
882
  }
869
883
  }
870
884
 
871
- // Get Solana address as bytes32
885
+ // Get Solana address as bytes32 (cross-platform)
872
886
  const solanaAddressBytes =
873
- '0x' + solanaWallet.publicKey.toBuffer().toString('hex');
887
+ '0x' + uint8ArrayToHex(solanaWallet.publicKey.toBytes());
874
888
 
875
889
  // Get current timestamp
876
890
  const signAt = Math.floor(Date.now() / 1000) - 30 * 60; // 30 minutes ago
@@ -930,8 +944,8 @@ export class DomainContext {
930
944
  decodeUTF8(solanaMsg),
931
945
  solanaWallet.secretKey
932
946
  );
933
- const sigFromAuthAddrHex =
934
- '0x' + Buffer.from(sigFromAuthAddr).toString('hex');
947
+ // Convert signature to hex (cross-platform)
948
+ const sigFromAuthAddrHex = '0x' + uint8ArrayToHex(sigFromAuthAddr);
935
949
 
936
950
  // Call contract
937
951
  const tx = await rootTagger.updateSolanaWallet(
@@ -979,18 +993,22 @@ export class DomainContext {
979
993
  try {
980
994
  // Import Solana libraries dynamically
981
995
  const { Keypair } = await import('@solana/web3.js');
996
+ const { base58btc } = await import('multiformats/bases/base58');
982
997
 
983
998
  // Parse Solana private key
984
999
  let solanaWallet: any;
985
1000
  try {
986
1001
  // Try base58 first
987
- const bs58 = await import('bs58');
988
- const secretKey = bs58.default.decode(solanaPrivateKey);
1002
+ // Solana uses plain base58, but multiformats requires 'z' prefix for multibase
1003
+ const base58Key = solanaPrivateKey.startsWith('z')
1004
+ ? solanaPrivateKey
1005
+ : 'z' + solanaPrivateKey;
1006
+ const secretKey = base58btc.decode(base58Key);
989
1007
  solanaWallet = Keypair.fromSecretKey(secretKey);
990
1008
  } catch {
991
1009
  // Try base64
992
1010
  try {
993
- const secretKey = Buffer.from(solanaPrivateKey, 'base64');
1011
+ const secretKey = base64ToUint8Array(solanaPrivateKey);
994
1012
  solanaWallet = Keypair.fromSecretKey(secretKey);
995
1013
  } catch {
996
1014
  // Try as JSON array
@@ -1001,9 +1019,9 @@ export class DomainContext {
1001
1019
  }
1002
1020
  }
1003
1021
 
1004
- // Get Solana address as bytes32
1022
+ // Get Solana address as bytes32 (cross-platform)
1005
1023
  const solanaAddressBytes =
1006
- '0x' + solanaWallet.publicKey.toBuffer().toString('hex');
1024
+ '0x' + uint8ArrayToHex(solanaWallet.publicKey.toBytes());
1007
1025
 
1008
1026
  // Get current timestamp
1009
1027
  const signAt = Math.floor(Date.now() / 1000) - 30 * 60; // 30 minutes ago
@@ -1104,11 +1122,8 @@ export class DomainContext {
1104
1122
  // Extract addresses from the result and convert to base58
1105
1123
  // Result is array of { algorithm, addr } where addr is bytes32
1106
1124
  return result.map((item: any) => {
1107
- // Remove 0x prefix and convert hex to buffer
1108
- const hexStr = item.addr.startsWith('0x')
1109
- ? item.addr.slice(2)
1110
- : item.addr;
1111
- const buffer = Buffer.from(hexStr, 'hex');
1125
+ // Convert hex to Uint8Array (cross-platform)
1126
+ const buffer = hexToUint8Array(item.addr);
1112
1127
  // Convert to Solana public key and then to base58
1113
1128
  return new PublicKey(buffer).toBase58();
1114
1129
  });
@@ -1423,26 +1438,16 @@ export function bytes4ToIpv4(bytes4Hex: string): string {
1423
1438
  */
1424
1439
  export function pemToDer(pem: string): string {
1425
1440
  // Remove PEM headers, footers, and whitespace
1426
- const base64 = pem
1441
+ const base64Str = pem
1427
1442
  .replace(/-----BEGIN.*?-----/g, '')
1428
1443
  .replace(/-----END.*?-----/g, '')
1429
1444
  .replace(/\s/g, '');
1430
1445
 
1431
- // Convert base64 to hex
1432
- let hexString: string;
1433
-
1434
- // Check if running in Node.js or browser
1435
- if (typeof Buffer !== 'undefined') {
1436
- // Node.js environment
1437
- const derBuffer = Buffer.from(base64, 'base64');
1438
- hexString = derBuffer.toString('hex');
1439
- } else {
1440
- // Browser environment
1441
- const binaryString = atob(base64);
1442
- hexString = Array.from(binaryString, (char) =>
1443
- char.charCodeAt(0).toString(16).padStart(2, '0')
1444
- ).join('');
1445
- }
1446
+ // Convert base64 to Uint8Array using cross-platform method
1447
+ const derBuffer = base64ToUint8Array(base64Str);
1448
+
1449
+ // Convert to hex string
1450
+ const hexString = uint8ArrayToHex(derBuffer);
1446
1451
 
1447
1452
  // Convert to hex string with 0x prefix
1448
1453
  return '0x' + hexString;
@@ -1457,31 +1462,14 @@ export function derToPem(derHex: string): string {
1457
1462
  // Remove '0x' prefix if present
1458
1463
  const hexString = derHex.startsWith('0x') ? derHex.slice(2) : derHex;
1459
1464
 
1460
- // Convert hex to base64
1461
- let base64: string;
1462
-
1463
- // Check if running in Node.js or browser
1464
- if (typeof Buffer !== 'undefined') {
1465
- // Node.js environment
1466
- const derBuffer = Buffer.from(hexString, 'hex');
1467
- base64 = derBuffer.toString('base64');
1468
- } else {
1469
- // Browser environment
1470
- // Convert hex string to byte array
1471
- const bytes = new Uint8Array(hexString.length / 2);
1472
- for (let i = 0; i < hexString.length; i += 2) {
1473
- bytes[i / 2] = parseInt(hexString.substr(i, 2), 16);
1474
- }
1475
- // Convert byte array to binary string
1476
- const binaryString = String.fromCharCode(...bytes);
1477
- // Convert to base64
1478
- base64 = btoa(binaryString);
1479
- }
1465
+ // Convert hex to base64 using cross-platform methods
1466
+ const bytes = hexToUint8Array(hexString);
1467
+ const base64Str = uint8ArrayToBase64(bytes);
1480
1468
 
1481
1469
  // Split base64 into 64-character lines for PEM format
1482
1470
  const lines: string[] = [];
1483
- for (let i = 0; i < base64.length; i += 64) {
1484
- lines.push(base64.slice(i, i + 64));
1471
+ for (let i = 0; i < base64Str.length; i += 64) {
1472
+ lines.push(base64Str.slice(i, i + 64));
1485
1473
  }
1486
1474
 
1487
1475
  // Construct PEM format with headers and footers
package/src/cli.ts CHANGED
@@ -1014,7 +1014,7 @@ async function registerSubdomain(
1014
1014
  );
1015
1015
  process.exit(1);
1016
1016
  }
1017
- mnemonic = generateMnemonic(wordCount);
1017
+ mnemonic = await generateMnemonic(wordCount);
1018
1018
 
1019
1019
  // Save mnemonic to file
1020
1020
  const mnemonicFile = './subdomain-mnemonic.txt';
@@ -1141,7 +1141,7 @@ async function transferDomain(
1141
1141
  );
1142
1142
  process.exit(1);
1143
1143
  }
1144
- mnemonic = generateMnemonic(wordCount);
1144
+ mnemonic = await generateMnemonic(wordCount);
1145
1145
 
1146
1146
  // Save mnemonic to file
1147
1147
  const mnemonicFile = './transfer-new-owner-mnemonic.txt';
@@ -1228,7 +1228,7 @@ async function cryptoGenerate(options: CliOptions): Promise<void> {
1228
1228
  process.exit(1);
1229
1229
  }
1230
1230
 
1231
- const mnemonic = generateMnemonic(wordCount);
1231
+ const mnemonic = await generateMnemonic(wordCount);
1232
1232
 
1233
1233
  // Save mnemonic to file
1234
1234
  const outputFile = options.output || './mnemonic.txt';
package/src/index.ts CHANGED
@@ -38,7 +38,12 @@ export {
38
38
  getDIDFromMnemonic,
39
39
  generateDIDKeyData,
40
40
  deriveDIDFromMnemonic,
41
- getEd25519JwkFromMnemonic
41
+ getEd25519JwkFromMnemonic,
42
+ // Cross-platform encoding utilities
43
+ base64ToUint8Array,
44
+ uint8ArrayToHex,
45
+ hexToUint8Array,
46
+ uint8ArrayToBase64
42
47
  } from './business';
43
48
 
44
49
  type Domain = PackageDomain.Domain;
@@ -6,13 +6,13 @@
6
6
  * compatibility across the entire Olares ecosystem.
7
7
  */
8
8
 
9
- import * as bip39 from 'bip39';
10
9
  import * as varint from 'varint';
11
10
  import { base58btc } from 'multiformats/bases/base58';
12
11
  import { base64url } from 'multiformats/bases/base64';
13
12
 
14
13
  // Browser globals type declaration
15
14
  declare const window: any;
15
+ declare const atob: (input: string) => string;
16
16
  declare const btoa: (input: string) => string;
17
17
 
18
18
  export interface RSAPublicKeyData {
@@ -26,6 +26,61 @@ export interface DIDKeyData {
26
26
  mnemonic: string;
27
27
  }
28
28
 
29
+ // ============================================================================
30
+ // Cross-platform encoding utilities (Browser + Node.js compatible)
31
+ // ============================================================================
32
+
33
+ /**
34
+ * Convert base64 string to Uint8Array (cross-platform)
35
+ * Works in both browser and Node.js environments
36
+ * @param base64 base64 encoded string
37
+ * @returns Uint8Array
38
+ */
39
+ export function base64ToUint8Array(base64: string): Uint8Array {
40
+ const binaryString = atob(base64);
41
+ const bytes = new Uint8Array(binaryString.length);
42
+ for (let i = 0; i < binaryString.length; i++) {
43
+ bytes[i] = binaryString.charCodeAt(i);
44
+ }
45
+ return bytes;
46
+ }
47
+
48
+ /**
49
+ * Convert Uint8Array to hex string (cross-platform)
50
+ * @param bytes Uint8Array to convert
51
+ * @returns hex string without '0x' prefix
52
+ */
53
+ export function uint8ArrayToHex(bytes: Uint8Array): string {
54
+ return Array.from(bytes)
55
+ .map((b) => b.toString(16).padStart(2, '0'))
56
+ .join('');
57
+ }
58
+
59
+ /**
60
+ * Convert hex string to Uint8Array (cross-platform)
61
+ * @param hex hex string (with or without '0x' prefix)
62
+ * @returns Uint8Array
63
+ */
64
+ export function hexToUint8Array(hex: string): Uint8Array {
65
+ const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
66
+ const bytes = new Uint8Array(cleanHex.length / 2);
67
+ for (let i = 0; i < cleanHex.length; i += 2) {
68
+ bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16);
69
+ }
70
+ return bytes;
71
+ }
72
+
73
+ /**
74
+ * Convert Uint8Array to base64 string (cross-platform)
75
+ * Works in both browser and Node.js environments
76
+ * @param bytes Uint8Array to convert
77
+ * @returns base64 encoded string
78
+ */
79
+ export function uint8ArrayToBase64(bytes: Uint8Array): string {
80
+ const binaryString = String.fromCharCode(...bytes);
81
+ return btoa(binaryString);
82
+ }
83
+
29
84
  // ============================================================================
30
85
  // Trust Wallet Core Management
31
86
  // ============================================================================
@@ -52,19 +107,9 @@ async function loadWalletCore(): Promise<any> {
52
107
  // Start loading
53
108
  loadingPromise = (async () => {
54
109
  try {
55
- // Check if running in browser or Node.js
56
- if (
57
- typeof window !== 'undefined' &&
58
- typeof require === 'undefined'
59
- ) {
60
- // Browser environment with ES modules
61
- const { initWasm } = await import('@trustwallet/wallet-core');
62
- walletCore = await initWasm();
63
- } else {
64
- // Node.js environment
65
- const { initWasm } = require('@trustwallet/wallet-core');
66
- walletCore = await initWasm();
67
- }
110
+ // Dynamic import works in both Node.js ESM and browser ESM
111
+ const { initWasm } = await import('@trustwallet/wallet-core');
112
+ walletCore = await initWasm();
68
113
 
69
114
  walletCoreLoaded = true;
70
115
  return walletCore;
@@ -84,23 +129,38 @@ async function loadWalletCore(): Promise<any> {
84
129
  // multicodec code for Ed25519 keys (0xed)
85
130
  const ED25519_CODEC_ID = varint.encode(parseInt('0xed', 16));
86
131
 
132
+ /**
133
+ * Simple mnemonic validation (checks word count)
134
+ * @param mnemonic BIP39 mnemonic phrase
135
+ * @returns true if mnemonic has valid word count (12, 15, 18, 21, or 24 words)
136
+ */
137
+ function validateMnemonic(mnemonic: string): boolean {
138
+ const words = mnemonic.trim().split(/\s+/);
139
+ const validWordCounts = [12, 15, 18, 21, 24];
140
+ return validWordCounts.includes(words.length);
141
+ }
142
+
87
143
  // ============================================================================
88
144
  // Mnemonic and Key Derivation Functions
89
145
  // ============================================================================
90
146
 
91
147
  /**
92
- * Generate a random BIP39 mnemonic phrase
148
+ * Generate a random BIP39 mnemonic phrase using Trust Wallet Core
149
+ * Works in both Node.js and browser environments
150
+ *
93
151
  * @param wordCount Number of words (12, 15, 18, 21, or 24), default is 12
94
- * @returns A mnemonic phrase string
152
+ * @returns A promise that resolves to a mnemonic phrase string
95
153
  *
96
154
  * @example
97
155
  * ```typescript
98
- * const mnemonic = generateMnemonic(12);
156
+ * const mnemonic = await generateMnemonic(12);
99
157
  * console.log(mnemonic);
100
158
  * // Output: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
101
159
  * ```
102
160
  */
103
- export function generateMnemonic(wordCount: number = 12): string {
161
+ export async function generateMnemonic(
162
+ wordCount: number = 12
163
+ ): Promise<string> {
104
164
  // Convert word count to entropy bits
105
165
  // 12 words = 128 bits, 15 words = 160 bits, etc.
106
166
  const strengthMap: Record<number, number> = {
@@ -118,7 +178,14 @@ export function generateMnemonic(wordCount: number = 12): string {
118
178
  );
119
179
  }
120
180
 
121
- return bip39.generateMnemonic(strength);
181
+ // Ensure Wallet Core is loaded
182
+ const core = await loadWalletCore();
183
+ const { HDWallet } = core;
184
+
185
+ const wallet = HDWallet.create(strength, '');
186
+ const mnemonic = wallet.mnemonic();
187
+
188
+ return mnemonic;
122
189
  }
123
190
 
124
191
  /**
@@ -138,8 +205,10 @@ export async function getEthereumAddressFromMnemonic(
138
205
  mnemonic: string
139
206
  ): Promise<string> {
140
207
  // Validate mnemonic
141
- if (!bip39.validateMnemonic(mnemonic)) {
142
- throw new Error('Invalid mnemonic phrase');
208
+ if (!validateMnemonic(mnemonic)) {
209
+ throw new Error(
210
+ 'Invalid mnemonic phrase: must have 12, 15, 18, 21, or 24 words'
211
+ );
143
212
  }
144
213
 
145
214
  const core = await loadWalletCore();
@@ -167,8 +236,10 @@ export async function getEVMPrivateKeyFromMnemonic(
167
236
  mnemonic: string
168
237
  ): Promise<string> {
169
238
  // Validate mnemonic
170
- if (!bip39.validateMnemonic(mnemonic)) {
171
- throw new Error('Invalid mnemonic phrase');
239
+ if (!validateMnemonic(mnemonic)) {
240
+ throw new Error(
241
+ 'Invalid mnemonic phrase: must have 12, 15, 18, 21, or 24 words'
242
+ );
172
243
  }
173
244
 
174
245
  const core = await loadWalletCore();
@@ -179,9 +250,9 @@ export async function getEVMPrivateKeyFromMnemonic(
179
250
  // Get private key for Ethereum
180
251
  const privateKeyData = wallet.getKeyForCoin(CoinType.ethereum);
181
252
 
182
- // Convert to hex string with 0x prefix
183
- const privateKeyHex =
184
- '0x' + Buffer.from(privateKeyData.data()).toString('hex');
253
+ // Convert to hex string with 0x prefix (cross-platform)
254
+ const privateKeyBytes = new Uint8Array(privateKeyData.data());
255
+ const privateKeyHex = '0x' + uint8ArrayToHex(privateKeyBytes);
185
256
 
186
257
  return privateKeyHex;
187
258
  }
@@ -230,8 +301,10 @@ async function getID(mnemonic: string): Promise<string> {
230
301
  */
231
302
  export async function getDIDFromMnemonic(mnemonic: string): Promise<string> {
232
303
  // Validate mnemonic
233
- if (!bip39.validateMnemonic(mnemonic)) {
234
- throw new Error('Invalid mnemonic phrase');
304
+ if (!validateMnemonic(mnemonic)) {
305
+ throw new Error(
306
+ 'Invalid mnemonic phrase: must have 12, 15, 18, 21, or 24 words'
307
+ );
235
308
  }
236
309
 
237
310
  const id = await getID(mnemonic);
@@ -278,8 +351,10 @@ export async function getEd25519JwkFromMnemonic(mnemonic: string): Promise<{
278
351
  privateJwk: any;
279
352
  }> {
280
353
  // Validate mnemonic
281
- if (!bip39.validateMnemonic(mnemonic)) {
282
- throw new Error('Invalid mnemonic phrase');
354
+ if (!validateMnemonic(mnemonic)) {
355
+ throw new Error(
356
+ 'Invalid mnemonic phrase: must have 12, 15, 18, 21, or 24 words'
357
+ );
283
358
  }
284
359
 
285
360
  const core = await loadWalletCore();
@@ -343,8 +418,10 @@ export async function deriveDIDFromMnemonic(mnemonic: string): Promise<{
343
418
  did: string;
344
419
  }> {
345
420
  // Validate mnemonic once upfront
346
- if (!bip39.validateMnemonic(mnemonic)) {
347
- throw new Error('Invalid mnemonic phrase');
421
+ if (!validateMnemonic(mnemonic)) {
422
+ throw new Error(
423
+ 'Invalid mnemonic phrase: must have 12, 15, 18, 21, or 24 words'
424
+ );
348
425
  }
349
426
 
350
427
  // Derive both in parallel for better performance
@@ -373,7 +450,7 @@ export async function deriveDIDFromMnemonic(mnemonic: string): Promise<{
373
450
  export async function generateDIDKeyData(
374
451
  wordCount: number = 12
375
452
  ): Promise<DIDKeyData> {
376
- const mnemonic = generateMnemonic(wordCount);
453
+ const mnemonic = await generateMnemonic(wordCount);
377
454
  const { owner, did } = await deriveDIDFromMnemonic(mnemonic);
378
455
 
379
456
  return {