@cheny56/node-client 1.0.7 → 1.0.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cheny56/node-client",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Client library for Quorum blockchain with Post-Quantum Cryptography (PQC) and Zero-Knowledge Proof (ZK) support",
5
5
  "main": "./index.cjs",
6
6
  "module": "./src/index.js",
@@ -42,7 +42,7 @@
42
42
  "license": "LGPL-3.0",
43
43
  "dependencies": {
44
44
  "ethers": "^5.7.2",
45
- "@noble/post-quantum": "^0.5.4"
45
+ "dilithium-crystals": "^1.0.0"
46
46
  },
47
47
  "peerDependencies": {},
48
48
  "devDependencies": {},
package/src/contract.js CHANGED
@@ -160,6 +160,10 @@ export class ERC20Token extends Contract {
160
160
  */
161
161
  async transferPQC(wallet, provider, to, amount, txOptions = {}) {
162
162
  const data = this.encodeFunctionData('transfer', [to, amount]);
163
+ // Ensure wallet has address
164
+ if (!wallet.address) {
165
+ await wallet._ensureKeyPair();
166
+ }
163
167
  const nonce = await provider.getTransactionCount(wallet.address, 'pending');
164
168
 
165
169
  const tx = new PQCLegacyTransaction({
@@ -172,7 +176,7 @@ export class ERC20Token extends Contract {
172
176
  data: data,
173
177
  });
174
178
 
175
- tx.sign(wallet);
179
+ await tx.sign(wallet);
176
180
  const serialized = tx.getHex();
177
181
  return await provider.sendRawTransaction(serialized);
178
182
  }
@@ -188,7 +192,9 @@ export class ERC20Token extends Contract {
188
192
  */
189
193
  async transferHybrid(wallet, provider, to, amount, txOptions = {}) {
190
194
  const data = this.encodeFunctionData('transfer', [to, amount]);
191
- const nonce = await provider.getTransactionCount(wallet.address, 'pending');
195
+ // Ensure wallet has address
196
+ const address = await wallet.getAddress();
197
+ const nonce = await provider.getTransactionCount(address, 'pending');
192
198
 
193
199
  const tx = new HybridLegacyTransaction({
194
200
  chainId: txOptions.chainId || 1337,
@@ -200,7 +206,7 @@ export class ERC20Token extends Contract {
200
206
  data: data,
201
207
  });
202
208
 
203
- tx.sign(wallet.ecdsaWallet, wallet.pqcWallet);
209
+ await tx.sign(wallet.ecdsaWallet, wallet.pqcWallet);
204
210
  const serialized = tx.getHex();
205
211
  return await provider.sendRawTransaction(serialized);
206
212
  }
@@ -8,19 +8,62 @@ import { createRequire } from 'module';
8
8
  import { TX_TYPE, PQC_TYPE, DEFAULT_CHAIN_ID } from './constants.js';
9
9
  import { encodeUint64, encodeBigInt, encodeSignature } from './utils/rlp.js';
10
10
 
11
- // Get ml_dsa65 from @noble/post-quantum using createRequire
12
- // This works around ES module import issues with @noble/post-quantum
11
+ // Import dilithium-crystals package
12
+ // Use createRequire to handle both ES modules and CommonJS
13
13
  const require = createRequire(import.meta.url);
14
14
 
15
- let ml_dsa65;
16
- try {
17
- // @noble/post-quantum uses CommonJS exports, so we use require
18
- ml_dsa65 = require('@noble/post-quantum/ml-dsa').ml_dsa65;
19
- } catch (e) {
20
- throw new Error(
21
- `Failed to import @noble/post-quantum: ${e.message}\n` +
22
- `Please ensure @noble/post-quantum is installed: npm install @noble/post-quantum`
23
- );
15
+ // Lazy load dilithium-crystals to avoid top-level await issues
16
+ let dilithiumCache = null;
17
+
18
+ async function getDilithium() {
19
+ if (dilithiumCache) {
20
+ return dilithiumCache;
21
+ }
22
+
23
+ try {
24
+ // Try ES module import first
25
+ const dilithiumModule = await import('dilithium-crystals');
26
+ dilithiumCache = dilithiumModule.default || dilithiumModule;
27
+ } catch (e) {
28
+ // Fallback to CommonJS require
29
+ try {
30
+ dilithiumCache = require('dilithium-crystals');
31
+ dilithiumCache = dilithiumCache.default || dilithiumCache;
32
+ } catch (e2) {
33
+ throw new Error(
34
+ `Failed to import dilithium-crystals: ${e.message}\n` +
35
+ `Please ensure dilithium-crystals is installed: npm install dilithium-crystals`
36
+ );
37
+ }
38
+ }
39
+
40
+ return dilithiumCache;
41
+ }
42
+
43
+ // Helper function to sign with dilithium
44
+ async function signWithDilithium(secretKey, hash) {
45
+ const dilithium = await getDilithium();
46
+ if (dilithium.sign) {
47
+ return dilithium.sign(secretKey, hash);
48
+ } else if (dilithium.signWithContext) {
49
+ const ctx = new TextEncoder().encode('ML-DSA-65');
50
+ return dilithium.signWithContext(secretKey, hash, ctx);
51
+ } else {
52
+ throw new Error('dilithium-crystals does not expose sign method');
53
+ }
54
+ }
55
+
56
+ // Helper function to verify with dilithium
57
+ async function verifyWithDilithium(publicKey, hash, signature) {
58
+ const dilithium = await getDilithium();
59
+ if (dilithium.verify) {
60
+ return dilithium.verify(publicKey, hash, signature);
61
+ } else if (dilithium.verifyWithContext) {
62
+ const ctx = new TextEncoder().encode('ML-DSA-65');
63
+ return dilithium.verifyWithContext(publicKey, hash, signature, ctx);
64
+ } else {
65
+ throw new Error('dilithium-crystals does not expose verify method');
66
+ }
24
67
  }
25
68
 
26
69
  /**
@@ -121,24 +164,22 @@ export class PQCLegacyTransaction extends BaseTransaction {
121
164
  /**
122
165
  * Sign with PQC wallet
123
166
  * @param {PQCWallet} wallet - PQC wallet
124
- * @returns {PQCLegacyTransaction} This transaction
167
+ * @returns {Promise<PQCLegacyTransaction>} This transaction
125
168
  */
126
- sign(wallet) {
169
+ async sign(wallet) {
127
170
  const hash = ethers.getBytes(this.getSigningHash());
128
- this.pqcPubKey = wallet.publicKey;
129
- const ctx = new TextEncoder().encode('ML-DSA-65');
130
- this.pqcSig = ml_dsa65.sign(wallet.secretKey, hash, ctx);
171
+ this.pqcPubKey = wallet.publicKey || (await wallet._ensureKeyPair(), wallet.publicKey);
172
+ this.pqcSig = await signWithDilithium(wallet.secretKey, hash);
131
173
  this.v = Number(this.chainId) * 2 + 35;
132
174
  this.r = '0x0';
133
175
  this.s = '0x0';
134
176
  return this;
135
177
  }
136
178
 
137
- verify() {
179
+ async verify() {
138
180
  if (!this.pqcSig || !this.pqcPubKey) return false;
139
181
  const hash = ethers.getBytes(this.getSigningHash());
140
- const ctx = new TextEncoder().encode('ML-DSA-65');
141
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
182
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
142
183
  }
143
184
 
144
185
  serialize() {
@@ -224,9 +265,9 @@ export class HybridLegacyTransaction extends BaseTransaction {
224
265
  * Sign with both ECDSA and PQC wallets
225
266
  * @param {ECDSAWallet} ecdsaWallet - ECDSA wallet
226
267
  * @param {PQCWallet} pqcWallet - PQC wallet
227
- * @returns {HybridLegacyTransaction} This transaction
268
+ * @returns {Promise<HybridLegacyTransaction>} This transaction
228
269
  */
229
- sign(ecdsaWallet, pqcWallet) {
270
+ async sign(ecdsaWallet, pqcWallet) {
230
271
  const hash = this.getSigningHash();
231
272
  const hashBytes = ethers.getBytes(hash);
232
273
 
@@ -238,18 +279,19 @@ export class HybridLegacyTransaction extends BaseTransaction {
238
279
  this.s = ecdsaSig.s;
239
280
 
240
281
  // PQC signature
241
- const ctx = new TextEncoder().encode('ML-DSA-65');
282
+ if (!pqcWallet.publicKey) {
283
+ await pqcWallet._ensureKeyPair();
284
+ }
242
285
  this.pqcPubKey = pqcWallet.publicKey;
243
- this.pqcSig = ml_dsa65.sign(pqcWallet.secretKey, hashBytes, ctx);
286
+ this.pqcSig = await signWithDilithium(pqcWallet.secretKey, hashBytes);
244
287
 
245
288
  return this;
246
289
  }
247
290
 
248
- verify() {
291
+ async verify() {
249
292
  if (!this.pqcSig || !this.pqcPubKey) return false;
250
293
  const hash = ethers.getBytes(this.getSigningHash());
251
- const ctx = new TextEncoder().encode('ML-DSA-65');
252
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
294
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
253
295
  }
254
296
 
255
297
  serialize() {
@@ -368,19 +410,20 @@ export class PQCAccessListTransaction extends BaseTransaction {
368
410
  return ethers.keccak256(ethers.hexlify(prefixed));
369
411
  }
370
412
 
371
- sign(wallet) {
413
+ async sign(wallet) {
372
414
  const hash = ethers.getBytes(this.getSigningHash());
415
+ if (!wallet.publicKey) {
416
+ await wallet._ensureKeyPair();
417
+ }
373
418
  this.pqcPubKey = wallet.publicKey;
374
- const ctx = new TextEncoder().encode('ML-DSA-65');
375
- this.pqcSig = ml_dsa65.sign(wallet.secretKey, hash, ctx);
419
+ this.pqcSig = await signWithDilithium(wallet.secretKey, hash);
376
420
  return this;
377
421
  }
378
422
 
379
- verify() {
423
+ async verify() {
380
424
  if (!this.pqcSig || !this.pqcPubKey) return false;
381
425
  const hash = ethers.getBytes(this.getSigningHash());
382
- const ctx = new TextEncoder().encode('ML-DSA-65');
383
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
426
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
384
427
  }
385
428
 
386
429
  serialize() {
@@ -515,19 +558,20 @@ export class PQCDynamicFeeTransaction extends BaseTransaction {
515
558
  return ethers.keccak256(ethers.hexlify(prefixed));
516
559
  }
517
560
 
518
- sign(wallet) {
561
+ async sign(wallet) {
519
562
  const hash = ethers.getBytes(this.getSigningHash());
563
+ if (!wallet.publicKey) {
564
+ await wallet._ensureKeyPair();
565
+ }
520
566
  this.pqcPubKey = wallet.publicKey;
521
- const ctx = new TextEncoder().encode('ML-DSA-65');
522
- this.pqcSig = ml_dsa65.sign(wallet.secretKey, hash, ctx);
567
+ this.pqcSig = await signWithDilithium(wallet.secretKey, hash);
523
568
  return this;
524
569
  }
525
570
 
526
- verify() {
571
+ async verify() {
527
572
  if (!this.pqcSig || !this.pqcPubKey) return false;
528
573
  const hash = ethers.getBytes(this.getSigningHash());
529
- const ctx = new TextEncoder().encode('ML-DSA-65');
530
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
574
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
531
575
  }
532
576
 
533
577
  serialize() {
package/src/wallet.js CHANGED
@@ -7,20 +7,37 @@ import { ethers } from 'ethers';
7
7
  import { createRequire } from 'module';
8
8
  import { derivePQCAddress, deriveHybridAddress } from './utils/address.js';
9
9
 
10
- // Get ml_dsa65 from @noble/post-quantum using createRequire
11
- // This works around ES module import issues with @noble/post-quantum
12
- // @noble/post-quantum uses CommonJS exports, so we use require() in ES modules
10
+ // Import dilithium-crystals package
11
+ // This package provides Dilithium3 (ML-DSA-65 equivalent) implementation
12
+ // Use createRequire to handle both ES modules and CommonJS
13
13
  const require = createRequire(import.meta.url);
14
14
 
15
- let ml_dsa65;
16
- try {
17
- // @noble/post-quantum uses CommonJS exports, so we use require
18
- ml_dsa65 = require('@noble/post-quantum/ml-dsa').ml_dsa65;
19
- } catch (e) {
20
- throw new Error(
21
- `Failed to import @noble/post-quantum: ${e.message}\n` +
22
- `Please ensure @noble/post-quantum is installed: npm install @noble/post-quantum`
23
- );
15
+ // Lazy load dilithium-crystals to avoid top-level await issues
16
+ let dilithiumCache = null;
17
+
18
+ async function getDilithium() {
19
+ if (dilithiumCache) {
20
+ return dilithiumCache;
21
+ }
22
+
23
+ try {
24
+ // Try ES module import first
25
+ const dilithiumModule = await import('dilithium-crystals');
26
+ dilithiumCache = dilithiumModule.default || dilithiumModule;
27
+ } catch (e) {
28
+ // Fallback to CommonJS require
29
+ try {
30
+ dilithiumCache = require('dilithium-crystals');
31
+ dilithiumCache = dilithiumCache.default || dilithiumCache;
32
+ } catch (e2) {
33
+ throw new Error(
34
+ `Failed to import dilithium-crystals: ${e.message}\n` +
35
+ `Please ensure dilithium-crystals is installed: npm install dilithium-crystals`
36
+ );
37
+ }
38
+ }
39
+
40
+ return dilithiumCache;
24
41
  }
25
42
 
26
43
  /**
@@ -70,12 +87,29 @@ export class PQCWallet {
70
87
  this.secretKey = secretKey instanceof Uint8Array ? secretKey : new Uint8Array(secretKey);
71
88
  this.publicKey = publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey);
72
89
  } else {
73
- // Generate new key pair
74
- const keyPair = ml_dsa65.keygen();
75
- this.secretKey = keyPair.secretKey;
76
- this.publicKey = keyPair.publicKey;
90
+ // Generate new key pair - will be done lazily on first use
91
+ this.secretKey = null;
92
+ this.publicKey = null;
93
+ this._keyPairGenerated = false;
77
94
  }
95
+ this.address = null; // Will be set after key generation
96
+ }
97
+
98
+ /**
99
+ * Initialize key pair if not already done
100
+ */
101
+ async _ensureKeyPair() {
102
+ if (this._keyPairGenerated) {
103
+ return;
104
+ }
105
+
106
+ const dilithium = await getDilithium();
107
+ // dilithium-crystals typically uses keygen() or generateKeyPair()
108
+ const keyPair = dilithium.keygen ? dilithium.keygen() : dilithium.generateKeyPair();
109
+ this.secretKey = keyPair.secretKey || keyPair.privateKey;
110
+ this.publicKey = keyPair.publicKey;
78
111
  this.address = derivePQCAddress(this.publicKey);
112
+ this._keyPairGenerated = true;
79
113
  }
80
114
 
81
115
  /**
@@ -92,43 +126,76 @@ export class PQCWallet {
92
126
  }
93
127
  const secret = secretKey instanceof Uint8Array ? secretKey : ethers.getBytes(secretKey);
94
128
  const pub = publicKey instanceof Uint8Array ? publicKey : ethers.getBytes(publicKey);
95
- return new PQCWallet(secret, pub);
129
+ const wallet = new PQCWallet(secret, pub);
130
+ wallet.address = derivePQCAddress(wallet.publicKey);
131
+ wallet._keyPairGenerated = true;
132
+ return wallet;
96
133
  }
97
134
 
98
135
  /**
99
136
  * Sign a message hash
100
137
  * @param {Uint8Array} hash - Message hash (32 bytes)
101
- * @returns {Uint8Array} Signature
138
+ * @returns {Promise<Uint8Array>} Signature
102
139
  */
103
- sign(hash) {
104
- const ctx = new TextEncoder().encode('ML-DSA-65');
105
- return ml_dsa65.sign(this.secretKey, hash, ctx);
140
+ async sign(hash) {
141
+ if (!this.secretKey) {
142
+ await this._ensureKeyPair();
143
+ }
144
+
145
+ const dilithium = await getDilithium();
146
+ // dilithium-crystals API: sign(secretKey, message) or signWithContext(secretKey, message, ctx)
147
+ if (dilithium.sign) {
148
+ return dilithium.sign(this.secretKey, hash);
149
+ } else if (dilithium.signWithContext) {
150
+ const ctx = new TextEncoder().encode('ML-DSA-65');
151
+ return dilithium.signWithContext(this.secretKey, hash, ctx);
152
+ } else {
153
+ throw new Error('dilithium-crystals does not expose sign method');
154
+ }
106
155
  }
107
156
 
108
157
  /**
109
158
  * Verify a signature
110
159
  * @param {Uint8Array} hash - Message hash
111
160
  * @param {Uint8Array} signature - Signature to verify
112
- * @returns {boolean} True if signature is valid
161
+ * @returns {Promise<boolean>} True if signature is valid
113
162
  */
114
- verify(hash, signature) {
115
- const ctx = new TextEncoder().encode('ML-DSA-65');
116
- return ml_dsa65.verify(this.publicKey, hash, signature, ctx);
163
+ async verify(hash, signature) {
164
+ if (!this.publicKey) {
165
+ throw new Error('Public key not set');
166
+ }
167
+
168
+ const dilithium = await getDilithium();
169
+ // dilithium-crystals API: verify(publicKey, message, signature) or verifyWithContext
170
+ if (dilithium.verify) {
171
+ return dilithium.verify(this.publicKey, hash, signature);
172
+ } else if (dilithium.verifyWithContext) {
173
+ const ctx = new TextEncoder().encode('ML-DSA-65');
174
+ return dilithium.verifyWithContext(this.publicKey, hash, signature, ctx);
175
+ } else {
176
+ throw new Error('dilithium-crystals does not expose verify method');
177
+ }
117
178
  }
118
179
 
119
180
  /**
120
181
  * Get public key as hex string
121
- * @returns {string} Public key (hex)
182
+ * @returns {Promise<string>} Public key (hex)
122
183
  */
123
- getPublicKeyHex() {
184
+ async getPublicKeyHex() {
185
+ if (!this.publicKey) {
186
+ await this._ensureKeyPair();
187
+ }
124
188
  return ethers.hexlify(this.publicKey);
125
189
  }
126
190
 
127
191
  /**
128
192
  * Get secret key as hex string (use with caution!)
129
- * @returns {string} Secret key (hex)
193
+ * @returns {Promise<string>} Secret key (hex)
130
194
  */
131
- getSecretKeyHex() {
195
+ async getSecretKeyHex() {
196
+ if (!this.secretKey) {
197
+ await this._ensureKeyPair();
198
+ }
132
199
  return ethers.hexlify(this.secretKey);
133
200
  }
134
201
  }
@@ -143,10 +210,29 @@ export class HybridWallet {
143
210
  }
144
211
  this.ecdsaWallet = ecdsaWallet instanceof ECDSAWallet ? ecdsaWallet : new ECDSAWallet(ecdsaWallet);
145
212
  this.pqcWallet = pqcWallet instanceof PQCWallet ? pqcWallet : new PQCWallet();
213
+ // Address will be computed when PQC wallet is ready
214
+ this.address = null;
215
+ }
216
+
217
+ /**
218
+ * Get the hybrid address (computed from ECDSA + PQC public keys)
219
+ * @returns {Promise<string>} Hybrid address
220
+ */
221
+ async getAddress() {
222
+ if (this.address) {
223
+ return this.address;
224
+ }
225
+
226
+ // Ensure PQC wallet has keys
227
+ if (!this.pqcWallet.publicKey) {
228
+ await this.pqcWallet._ensureKeyPair();
229
+ }
230
+
146
231
  this.address = deriveHybridAddress(
147
232
  ethers.getBytes(this.ecdsaWallet.getPublicKey()),
148
233
  this.pqcWallet.publicKey
149
234
  );
235
+ return this.address;
150
236
  }
151
237
 
152
238
  /**
@@ -159,7 +245,12 @@ export class HybridWallet {
159
245
  static fromKeys(ecdsaPrivateKey, pqcSecretKey, pqcPublicKey) {
160
246
  const ecdsaWallet = new ECDSAWallet(ecdsaPrivateKey);
161
247
  const pqcWallet = new PQCWallet(pqcSecretKey, pqcPublicKey);
162
- return new HybridWallet(ecdsaWallet, pqcWallet);
248
+ const wallet = new HybridWallet(ecdsaWallet, pqcWallet);
249
+ wallet.address = deriveHybridAddress(
250
+ ethers.getBytes(ecdsaWallet.getPublicKey()),
251
+ pqcWallet.publicKey
252
+ );
253
+ return wallet;
163
254
  }
164
255
 
165
256
  /**