@cheny56/node-client 1.0.6 → 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.6",
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
  }
@@ -4,11 +4,68 @@
4
4
  */
5
5
 
6
6
  import { ethers } from 'ethers';
7
- // @ts-ignore - @noble/post-quantum may not have TypeScript definitions
8
- import { ml_dsa65 } from '@noble/post-quantum/ml-dsa';
7
+ import { createRequire } from 'module';
9
8
  import { TX_TYPE, PQC_TYPE, DEFAULT_CHAIN_ID } from './constants.js';
10
9
  import { encodeUint64, encodeBigInt, encodeSignature } from './utils/rlp.js';
11
10
 
11
+ // Import dilithium-crystals package
12
+ // Use createRequire to handle both ES modules and CommonJS
13
+ const require = createRequire(import.meta.url);
14
+
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
+ }
67
+ }
68
+
12
69
  /**
13
70
  * Base Transaction class
14
71
  */
@@ -107,24 +164,22 @@ export class PQCLegacyTransaction extends BaseTransaction {
107
164
  /**
108
165
  * Sign with PQC wallet
109
166
  * @param {PQCWallet} wallet - PQC wallet
110
- * @returns {PQCLegacyTransaction} This transaction
167
+ * @returns {Promise<PQCLegacyTransaction>} This transaction
111
168
  */
112
- sign(wallet) {
169
+ async sign(wallet) {
113
170
  const hash = ethers.getBytes(this.getSigningHash());
114
- this.pqcPubKey = wallet.publicKey;
115
- const ctx = new TextEncoder().encode('ML-DSA-65');
116
- 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);
117
173
  this.v = Number(this.chainId) * 2 + 35;
118
174
  this.r = '0x0';
119
175
  this.s = '0x0';
120
176
  return this;
121
177
  }
122
178
 
123
- verify() {
179
+ async verify() {
124
180
  if (!this.pqcSig || !this.pqcPubKey) return false;
125
181
  const hash = ethers.getBytes(this.getSigningHash());
126
- const ctx = new TextEncoder().encode('ML-DSA-65');
127
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
182
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
128
183
  }
129
184
 
130
185
  serialize() {
@@ -210,9 +265,9 @@ export class HybridLegacyTransaction extends BaseTransaction {
210
265
  * Sign with both ECDSA and PQC wallets
211
266
  * @param {ECDSAWallet} ecdsaWallet - ECDSA wallet
212
267
  * @param {PQCWallet} pqcWallet - PQC wallet
213
- * @returns {HybridLegacyTransaction} This transaction
268
+ * @returns {Promise<HybridLegacyTransaction>} This transaction
214
269
  */
215
- sign(ecdsaWallet, pqcWallet) {
270
+ async sign(ecdsaWallet, pqcWallet) {
216
271
  const hash = this.getSigningHash();
217
272
  const hashBytes = ethers.getBytes(hash);
218
273
 
@@ -224,18 +279,19 @@ export class HybridLegacyTransaction extends BaseTransaction {
224
279
  this.s = ecdsaSig.s;
225
280
 
226
281
  // PQC signature
227
- const ctx = new TextEncoder().encode('ML-DSA-65');
282
+ if (!pqcWallet.publicKey) {
283
+ await pqcWallet._ensureKeyPair();
284
+ }
228
285
  this.pqcPubKey = pqcWallet.publicKey;
229
- this.pqcSig = ml_dsa65.sign(pqcWallet.secretKey, hashBytes, ctx);
286
+ this.pqcSig = await signWithDilithium(pqcWallet.secretKey, hashBytes);
230
287
 
231
288
  return this;
232
289
  }
233
290
 
234
- verify() {
291
+ async verify() {
235
292
  if (!this.pqcSig || !this.pqcPubKey) return false;
236
293
  const hash = ethers.getBytes(this.getSigningHash());
237
- const ctx = new TextEncoder().encode('ML-DSA-65');
238
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
294
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
239
295
  }
240
296
 
241
297
  serialize() {
@@ -354,19 +410,20 @@ export class PQCAccessListTransaction extends BaseTransaction {
354
410
  return ethers.keccak256(ethers.hexlify(prefixed));
355
411
  }
356
412
 
357
- sign(wallet) {
413
+ async sign(wallet) {
358
414
  const hash = ethers.getBytes(this.getSigningHash());
415
+ if (!wallet.publicKey) {
416
+ await wallet._ensureKeyPair();
417
+ }
359
418
  this.pqcPubKey = wallet.publicKey;
360
- const ctx = new TextEncoder().encode('ML-DSA-65');
361
- this.pqcSig = ml_dsa65.sign(wallet.secretKey, hash, ctx);
419
+ this.pqcSig = await signWithDilithium(wallet.secretKey, hash);
362
420
  return this;
363
421
  }
364
422
 
365
- verify() {
423
+ async verify() {
366
424
  if (!this.pqcSig || !this.pqcPubKey) return false;
367
425
  const hash = ethers.getBytes(this.getSigningHash());
368
- const ctx = new TextEncoder().encode('ML-DSA-65');
369
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
426
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
370
427
  }
371
428
 
372
429
  serialize() {
@@ -501,19 +558,20 @@ export class PQCDynamicFeeTransaction extends BaseTransaction {
501
558
  return ethers.keccak256(ethers.hexlify(prefixed));
502
559
  }
503
560
 
504
- sign(wallet) {
561
+ async sign(wallet) {
505
562
  const hash = ethers.getBytes(this.getSigningHash());
563
+ if (!wallet.publicKey) {
564
+ await wallet._ensureKeyPair();
565
+ }
506
566
  this.pqcPubKey = wallet.publicKey;
507
- const ctx = new TextEncoder().encode('ML-DSA-65');
508
- this.pqcSig = ml_dsa65.sign(wallet.secretKey, hash, ctx);
567
+ this.pqcSig = await signWithDilithium(wallet.secretKey, hash);
509
568
  return this;
510
569
  }
511
570
 
512
- verify() {
571
+ async verify() {
513
572
  if (!this.pqcSig || !this.pqcPubKey) return false;
514
573
  const hash = ethers.getBytes(this.getSigningHash());
515
- const ctx = new TextEncoder().encode('ML-DSA-65');
516
- return ml_dsa65.verify(this.pqcPubKey, hash, this.pqcSig, ctx);
574
+ return await verifyWithDilithium(this.pqcPubKey, hash, this.pqcSig);
517
575
  }
518
576
 
519
577
  serialize() {
package/src/wallet.js CHANGED
@@ -4,10 +4,42 @@
4
4
  */
5
5
 
6
6
  import { ethers } from 'ethers';
7
- // @ts-ignore - @noble/post-quantum may not have TypeScript definitions
8
- import { ml_dsa65 } from '@noble/post-quantum/ml-dsa';
7
+ import { createRequire } from 'module';
9
8
  import { derivePQCAddress, deriveHybridAddress } from './utils/address.js';
10
9
 
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
+ const require = createRequire(import.meta.url);
14
+
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
+
11
43
  /**
12
44
  * ECDSA Wallet (standard Ethereum wallet)
13
45
  */
@@ -55,12 +87,29 @@ export class PQCWallet {
55
87
  this.secretKey = secretKey instanceof Uint8Array ? secretKey : new Uint8Array(secretKey);
56
88
  this.publicKey = publicKey instanceof Uint8Array ? publicKey : new Uint8Array(publicKey);
57
89
  } else {
58
- // Generate new key pair
59
- const keyPair = ml_dsa65.keygen();
60
- this.secretKey = keyPair.secretKey;
61
- 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;
62
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;
63
111
  this.address = derivePQCAddress(this.publicKey);
112
+ this._keyPairGenerated = true;
64
113
  }
65
114
 
66
115
  /**
@@ -77,43 +126,76 @@ export class PQCWallet {
77
126
  }
78
127
  const secret = secretKey instanceof Uint8Array ? secretKey : ethers.getBytes(secretKey);
79
128
  const pub = publicKey instanceof Uint8Array ? publicKey : ethers.getBytes(publicKey);
80
- 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;
81
133
  }
82
134
 
83
135
  /**
84
136
  * Sign a message hash
85
137
  * @param {Uint8Array} hash - Message hash (32 bytes)
86
- * @returns {Uint8Array} Signature
138
+ * @returns {Promise<Uint8Array>} Signature
87
139
  */
88
- sign(hash) {
89
- const ctx = new TextEncoder().encode('ML-DSA-65');
90
- 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
+ }
91
155
  }
92
156
 
93
157
  /**
94
158
  * Verify a signature
95
159
  * @param {Uint8Array} hash - Message hash
96
160
  * @param {Uint8Array} signature - Signature to verify
97
- * @returns {boolean} True if signature is valid
161
+ * @returns {Promise<boolean>} True if signature is valid
98
162
  */
99
- verify(hash, signature) {
100
- const ctx = new TextEncoder().encode('ML-DSA-65');
101
- 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
+ }
102
178
  }
103
179
 
104
180
  /**
105
181
  * Get public key as hex string
106
- * @returns {string} Public key (hex)
182
+ * @returns {Promise<string>} Public key (hex)
107
183
  */
108
- getPublicKeyHex() {
184
+ async getPublicKeyHex() {
185
+ if (!this.publicKey) {
186
+ await this._ensureKeyPair();
187
+ }
109
188
  return ethers.hexlify(this.publicKey);
110
189
  }
111
190
 
112
191
  /**
113
192
  * Get secret key as hex string (use with caution!)
114
- * @returns {string} Secret key (hex)
193
+ * @returns {Promise<string>} Secret key (hex)
115
194
  */
116
- getSecretKeyHex() {
195
+ async getSecretKeyHex() {
196
+ if (!this.secretKey) {
197
+ await this._ensureKeyPair();
198
+ }
117
199
  return ethers.hexlify(this.secretKey);
118
200
  }
119
201
  }
@@ -128,10 +210,29 @@ export class HybridWallet {
128
210
  }
129
211
  this.ecdsaWallet = ecdsaWallet instanceof ECDSAWallet ? ecdsaWallet : new ECDSAWallet(ecdsaWallet);
130
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
+
131
231
  this.address = deriveHybridAddress(
132
232
  ethers.getBytes(this.ecdsaWallet.getPublicKey()),
133
233
  this.pqcWallet.publicKey
134
234
  );
235
+ return this.address;
135
236
  }
136
237
 
137
238
  /**
@@ -144,7 +245,12 @@ export class HybridWallet {
144
245
  static fromKeys(ecdsaPrivateKey, pqcSecretKey, pqcPublicKey) {
145
246
  const ecdsaWallet = new ECDSAWallet(ecdsaPrivateKey);
146
247
  const pqcWallet = new PQCWallet(pqcSecretKey, pqcPublicKey);
147
- 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;
148
254
  }
149
255
 
150
256
  /**