@btc-vision/transaction 1.6.19 → 1.7.1

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.
Files changed (155) hide show
  1. package/browser/index.js +1 -1
  2. package/browser/index.js.LICENSE.txt +2 -0
  3. package/browser/src/_version.d.ts +1 -0
  4. package/browser/{epoch → src/epoch}/interfaces/IChallengeSolution.d.ts +2 -0
  5. package/browser/{keypair → src/keypair}/Address.d.ts +7 -4
  6. package/browser/{keypair → src/keypair}/AddressVerificator.d.ts +3 -0
  7. package/browser/{keypair → src/keypair}/EcKeyPair.d.ts +3 -2
  8. package/browser/{keypair → src/keypair}/MessageSigner.d.ts +9 -0
  9. package/browser/src/keypair/Wallet.d.ts +47 -0
  10. package/browser/{keypair → src/keypair}/interfaces/IWallet.d.ts +2 -0
  11. package/browser/src/mnemonic/BIPStandard.d.ts +8 -0
  12. package/browser/src/mnemonic/Mnemonic.d.ts +34 -0
  13. package/browser/src/mnemonic/MnemonicStrength.d.ts +7 -0
  14. package/browser/{opnet.d.ts → src/opnet.d.ts} +5 -0
  15. package/browser/src/transaction/browser/types/OPWallet.d.ts +14 -0
  16. package/browser/test/address.test.d.ts +1 -0
  17. package/browser/test/addressverificator-mldsa.test.d.ts +1 -0
  18. package/browser/test/derivePath.test.d.ts +1 -0
  19. package/browser/test/messagesigner-mldsa.test.d.ts +1 -0
  20. package/browser/test/messagesigner-schnorr.test.d.ts +1 -0
  21. package/browser/test/network-awareness.test.d.ts +1 -0
  22. package/build/_version.d.ts +1 -1
  23. package/build/_version.js +1 -1
  24. package/build/crypto/crypto-browser.d.ts +11 -0
  25. package/build/crypto/crypto-browser.js +56 -0
  26. package/build/epoch/ChallengeSolution.js +3 -2
  27. package/build/epoch/interfaces/IChallengeSolution.d.ts +2 -0
  28. package/build/keypair/Address.d.ts +7 -4
  29. package/build/keypair/Address.js +88 -37
  30. package/build/keypair/AddressVerificator.d.ts +3 -0
  31. package/build/keypair/AddressVerificator.js +49 -1
  32. package/build/keypair/EcKeyPair.d.ts +3 -2
  33. package/build/keypair/EcKeyPair.js +17 -3
  34. package/build/keypair/MessageSigner.d.ts +9 -0
  35. package/build/keypair/MessageSigner.js +23 -0
  36. package/build/keypair/Wallet.d.ts +20 -3
  37. package/build/keypair/Wallet.js +108 -9
  38. package/build/keypair/interfaces/IWallet.d.ts +2 -0
  39. package/build/mnemonic/BIPStandard.d.ts +8 -0
  40. package/build/mnemonic/BIPStandard.js +24 -0
  41. package/build/mnemonic/Mnemonic.d.ts +34 -0
  42. package/build/mnemonic/Mnemonic.js +140 -0
  43. package/build/mnemonic/MnemonicStrength.d.ts +7 -0
  44. package/build/mnemonic/MnemonicStrength.js +8 -0
  45. package/build/opnet.d.ts +5 -0
  46. package/build/opnet.js +5 -0
  47. package/build/transaction/browser/types/OPWallet.d.ts +14 -0
  48. package/build/transaction/browser/types/OPWallet.js +6 -0
  49. package/documentation/README.md +32 -0
  50. package/documentation/quantum-support/01-introduction.md +88 -0
  51. package/documentation/quantum-support/02-mnemonic-and-wallet.md +445 -0
  52. package/documentation/quantum-support/03-address-generation.md +329 -0
  53. package/documentation/quantum-support/04-message-signing.md +623 -0
  54. package/documentation/quantum-support/05-address-verification.md +307 -0
  55. package/documentation/quantum-support/README.md +65 -0
  56. package/gulpfile.js +2 -2
  57. package/package.json +25 -17
  58. package/src/_version.ts +1 -1
  59. package/src/epoch/ChallengeSolution.ts +3 -2
  60. package/src/epoch/interfaces/IChallengeSolution.ts +2 -0
  61. package/src/keypair/Address.ts +145 -43
  62. package/src/keypair/AddressVerificator.ts +87 -2
  63. package/src/keypair/EcKeyPair.ts +58 -6
  64. package/src/keypair/MessageSigner.ts +58 -0
  65. package/src/keypair/Wallet.ts +339 -57
  66. package/src/keypair/interfaces/IWallet.ts +13 -3
  67. package/src/mnemonic/BIPStandard.ts +92 -0
  68. package/src/mnemonic/Mnemonic.ts +465 -0
  69. package/src/mnemonic/MnemonicStrength.ts +12 -0
  70. package/src/network/ChainId.ts +1 -4
  71. package/src/opnet.ts +17 -0
  72. package/src/transaction/browser/types/OPWallet.ts +73 -0
  73. package/test/address.test.ts +1068 -0
  74. package/test/addressverificator-mldsa.test.ts +473 -0
  75. package/test/derivePath.test.ts +513 -0
  76. package/test/messagesigner-mldsa.test.ts +1060 -0
  77. package/test/messagesigner-schnorr.test.ts +1011 -0
  78. package/test/network-awareness.test.ts +163 -0
  79. package/tsconfig.json +1 -1
  80. package/vitest.config.ts +21 -0
  81. package/browser/_version.d.ts +0 -1
  82. package/browser/keypair/Wallet.d.ts +0 -30
  83. package/doc/README.md +0 -0
  84. /package/browser/{abi → src/abi}/ABICoder.d.ts +0 -0
  85. /package/browser/{buffer → src/buffer}/BinaryReader.d.ts +0 -0
  86. /package/browser/{buffer → src/buffer}/BinaryWriter.d.ts +0 -0
  87. /package/browser/{bytecode → src/bytecode}/Compressor.d.ts +0 -0
  88. /package/browser/{consensus → src/consensus}/Consensus.d.ts +0 -0
  89. /package/browser/{consensus → src/consensus}/ConsensusConfig.d.ts +0 -0
  90. /package/browser/{consensus → src/consensus}/metadata/RoswellConsensus.d.ts +0 -0
  91. /package/browser/{crypto → src/crypto}/crypto-browser.d.ts +0 -0
  92. /package/browser/{crypto → src/crypto}/crypto.d.ts +0 -0
  93. /package/browser/{deterministic → src/deterministic}/AddressMap.d.ts +0 -0
  94. /package/browser/{deterministic → src/deterministic}/AddressSet.d.ts +0 -0
  95. /package/browser/{deterministic → src/deterministic}/DeterministicMap.d.ts +0 -0
  96. /package/browser/{deterministic → src/deterministic}/DeterministicSet.d.ts +0 -0
  97. /package/browser/{deterministic → src/deterministic}/Map.d.ts +0 -0
  98. /package/browser/{epoch → src/epoch}/ChallengeSolution.d.ts +0 -0
  99. /package/browser/{epoch → src/epoch}/validator/EpochValidator.d.ts +0 -0
  100. /package/browser/{event → src/event}/NetEvent.d.ts +0 -0
  101. /package/browser/{generators → src/generators}/AddressGenerator.d.ts +0 -0
  102. /package/browser/{generators → src/generators}/Features.d.ts +0 -0
  103. /package/browser/{generators → src/generators}/Generator.d.ts +0 -0
  104. /package/browser/{generators → src/generators}/builders/CalldataGenerator.d.ts +0 -0
  105. /package/browser/{generators → src/generators}/builders/CustomGenerator.d.ts +0 -0
  106. /package/browser/{generators → src/generators}/builders/DeploymentGenerator.d.ts +0 -0
  107. /package/browser/{generators → src/generators}/builders/LegacyCalldataGenerator.d.ts +0 -0
  108. /package/browser/{generators → src/generators}/builders/MultiSignGenerator.d.ts +0 -0
  109. /package/browser/{generators → src/generators}/builders/P2WDAGenerator.d.ts +0 -0
  110. /package/browser/{index.d.ts → src/index.d.ts} +0 -0
  111. /package/browser/{keypair → src/keypair}/Secp256k1PointDeriver.d.ts +0 -0
  112. /package/browser/{metadata → src/metadata}/ContractBaseMetadata.d.ts +0 -0
  113. /package/browser/{metadata → src/metadata}/tokens.d.ts +0 -0
  114. /package/browser/{network → src/network}/ChainId.d.ts +0 -0
  115. /package/browser/{p2wda → src/p2wda}/P2WDADetector.d.ts +0 -0
  116. /package/browser/{signer → src/signer}/SignerUtils.d.ts +0 -0
  117. /package/browser/{signer → src/signer}/TweakedSigner.d.ts +0 -0
  118. /package/browser/{transaction → src/transaction}/ContractAddress.d.ts +0 -0
  119. /package/browser/{transaction → src/transaction}/TransactionFactory.d.ts +0 -0
  120. /package/browser/{transaction → src/transaction}/browser/BrowserSignerBase.d.ts +0 -0
  121. /package/browser/{transaction → src/transaction}/browser/Web3Provider.d.ts +0 -0
  122. /package/browser/{transaction → src/transaction}/browser/extensions/UnisatSigner.d.ts +0 -0
  123. /package/browser/{transaction → src/transaction}/browser/extensions/XverseSigner.d.ts +0 -0
  124. /package/browser/{transaction → src/transaction}/browser/types/Unisat.d.ts +0 -0
  125. /package/browser/{transaction → src/transaction}/browser/types/Xverse.d.ts +0 -0
  126. /package/browser/{transaction → src/transaction}/builders/CancelTransaction.d.ts +0 -0
  127. /package/browser/{transaction → src/transaction}/builders/ChallengeSolutionTransaction.d.ts +0 -0
  128. /package/browser/{transaction → src/transaction}/builders/CustomScriptTransaction.d.ts +0 -0
  129. /package/browser/{transaction → src/transaction}/builders/DeploymentTransaction.d.ts +0 -0
  130. /package/browser/{transaction → src/transaction}/builders/FundingTransaction.d.ts +0 -0
  131. /package/browser/{transaction → src/transaction}/builders/InteractionTransaction.d.ts +0 -0
  132. /package/browser/{transaction → src/transaction}/builders/InteractionTransactionP2WDA.d.ts +0 -0
  133. /package/browser/{transaction → src/transaction}/builders/MultiSignTransaction.d.ts +0 -0
  134. /package/browser/{transaction → src/transaction}/builders/SharedInteractionTransaction.d.ts +0 -0
  135. /package/browser/{transaction → src/transaction}/builders/TransactionBuilder.d.ts +0 -0
  136. /package/browser/{transaction → src/transaction}/enums/TransactionType.d.ts +0 -0
  137. /package/browser/{transaction → src/transaction}/interfaces/ITransactionParameters.d.ts +0 -0
  138. /package/browser/{transaction → src/transaction}/interfaces/Tap.d.ts +0 -0
  139. /package/browser/{transaction → src/transaction}/mineable/IP2WSHAddress.d.ts +0 -0
  140. /package/browser/{transaction → src/transaction}/mineable/TimelockGenerator.d.ts +0 -0
  141. /package/browser/{transaction → src/transaction}/processor/PsbtTransaction.d.ts +0 -0
  142. /package/browser/{transaction → src/transaction}/psbt/PSBTTypes.d.ts +0 -0
  143. /package/browser/{transaction → src/transaction}/shared/P2TR_MS.d.ts +0 -0
  144. /package/browser/{transaction → src/transaction}/shared/TweakedTransaction.d.ts +0 -0
  145. /package/browser/{utils → src/utils}/BitcoinUtils.d.ts +0 -0
  146. /package/browser/{utils → src/utils}/BufferHelper.d.ts +0 -0
  147. /package/browser/{utils → src/utils}/StringToBuffer.d.ts +0 -0
  148. /package/browser/{utils → src/utils}/lengths.d.ts +0 -0
  149. /package/browser/{utils → src/utils}/types.d.ts +0 -0
  150. /package/browser/{utxo → src/utxo}/OPNetLimitedProvider.d.ts +0 -0
  151. /package/browser/{utxo → src/utxo}/interfaces/BroadcastResponse.d.ts +0 -0
  152. /package/browser/{utxo → src/utxo}/interfaces/IUTXO.d.ts +0 -0
  153. /package/browser/{verification → src/verification}/TapscriptVerificator.d.ts +0 -0
  154. /package/{doc → documentation}/addresses/P2OP.md +0 -0
  155. /package/{doc → documentation}/addresses/P2WDA.md +0 -0
@@ -8,9 +8,16 @@ import { BitcoinUtils } from '../utils/BitcoinUtils.js';
8
8
  import { TimeLockGenerator } from '../transaction/mineable/TimelockGenerator.js';
9
9
  import { IP2WSHAddress } from '../transaction/mineable/IP2WSHAddress.js';
10
10
  import { P2WDADetector } from '../p2wda/P2WDADetector.js';
11
+ import { sha256 } from '@noble/hashes/sha2';
11
12
 
12
13
  /**
13
- * Objects of type "Address" are the representation of tweaked public keys. They can be converted to different address formats.
14
+ * Objects of type "Address" represent hashed ML-DSA (quantum) public keys (using SHA256 of quantum keys) and maintain classical public keys separately.
15
+ * This class supports a hybrid quantum-classical architecture, allowing conversion to different address formats and management of both key types.
16
+ *
17
+ * The Address internally stores the SHA256 hash of the ML-DSA public key as its primary content, while maintaining
18
+ * the classical public key in a separate field. This enables quantum-resistant addressing while preserving
19
+ * compatibility with traditional Bitcoin cryptography.
20
+ *
14
21
  * @category KeyPair
15
22
  */
16
23
  export class Address extends Uint8Array {
@@ -22,15 +29,23 @@ export class Address extends Uint8Array {
22
29
  #uncompressed: UncompressedPublicKey | undefined;
23
30
  #tweakedUncompressed: Buffer | undefined;
24
31
  #p2wda: IP2WSHAddress | undefined;
32
+ #mldsaPublicKey: Uint8Array | undefined;
25
33
 
26
- public constructor(bytes?: ArrayLike<number>) {
34
+ private classicPublicKey: Uint8Array | undefined;
35
+
36
+ public constructor(mldsaPublicKey?: ArrayLike<number>, publicKeyOrTweak?: ArrayLike<number>) {
27
37
  super(ADDRESS_BYTE_LENGTH);
28
38
 
29
- if (!bytes) {
39
+ if (!mldsaPublicKey) {
30
40
  return;
31
41
  }
32
42
 
33
- this.set(bytes);
43
+ if (publicKeyOrTweak) {
44
+ this.classicPublicKey = new Uint8Array(publicKeyOrTweak.length);
45
+ this.classicPublicKey.set(publicKeyOrTweak);
46
+ }
47
+
48
+ this.set(mldsaPublicKey);
34
49
  }
35
50
 
36
51
  /**
@@ -41,13 +56,17 @@ export class Address extends Uint8Array {
41
56
  return this.#originalPublicKey;
42
57
  }
43
58
 
59
+ public get mldsaPublicKey(): Uint8Array | undefined {
60
+ return this.#mldsaPublicKey;
61
+ }
62
+
44
63
  /**
45
64
  * Get the key pair for the address
46
65
  * @description This is only for internal use. Please use address.tweakedBytes instead.
47
66
  */
48
67
  private get keyPair(): ECPairInterface {
49
68
  if (!this.#keyPair) {
50
- throw new Error('Public key not set for address');
69
+ throw new Error('Classical public key not set for address');
51
70
  }
52
71
 
53
72
  return this.#keyPair;
@@ -55,35 +74,48 @@ export class Address extends Uint8Array {
55
74
 
56
75
  public static dead(): Address {
57
76
  return Address.fromString(
77
+ '0x0000000000000000000000000000000000000000000000000000000000000000', // DEAD ADDRESS
58
78
  '0x04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f',
59
79
  );
60
80
  }
61
81
 
62
- public static zero(): Address {
63
- return new Address();
64
- }
65
-
66
82
  /**
67
83
  * Create an address from a hex string
68
- * @param {string} pubKey The public key
84
+ * @param {string} mldsaPublicKey The ml-dsa public key in hex format
85
+ * @param {string} classicPublicKey The classical public key in hex format
69
86
  * @returns {Address} The address
70
87
  */
71
- public static fromString(pubKey: string): Address {
72
- if (!pubKey) {
88
+ public static fromString(mldsaPublicKey: string, classicPublicKey?: string): Address {
89
+ if (!mldsaPublicKey) {
73
90
  throw new Error('Invalid public key');
74
91
  }
75
92
 
76
- if (pubKey.startsWith('0x')) {
77
- pubKey = pubKey.slice(2);
93
+ if (mldsaPublicKey.startsWith('0x')) {
94
+ mldsaPublicKey = mldsaPublicKey.slice(2);
78
95
  }
79
96
 
80
- if (!BitcoinUtils.isValidHex(pubKey)) {
97
+ if (!BitcoinUtils.isValidHex(mldsaPublicKey)) {
81
98
  throw new Error(
82
99
  'You must only pass public keys in hexadecimal format. If you have an address such as bc1q... you must convert it to a public key first. Please refer to await provider.getPublicKeyInfo("bc1q..."). If the public key associated with the address is not found, you must force the user to enter the destination public key. It looks like: 0x020373626d317ae8788ce3280b491068610d840c23ecb64c14075bbb9f670af52c.',
83
100
  );
84
101
  }
85
102
 
86
- return new Address(Buffer.from(pubKey, 'hex'));
103
+ let classicBuffer: Buffer | undefined;
104
+ if (classicPublicKey) {
105
+ if (classicPublicKey.startsWith('0x')) {
106
+ classicPublicKey = classicPublicKey.slice(2);
107
+ }
108
+
109
+ if (!BitcoinUtils.isValidHex(classicPublicKey)) {
110
+ throw new Error(
111
+ 'You must only pass classical public keys in hexadecimal format. If you have an address such as bc1q... you must convert it to a public key first. Please refer to await provider.getPublicKeyInfo("bc1q..."). If the public key associated with the address is not found, you must force the user to enter the destination public key. It looks like: 0x020373626d317ae8788ce3280b491068610d840c23ecb64c14075bbb9f670af52c.',
112
+ );
113
+ }
114
+
115
+ classicBuffer = Buffer.from(classicPublicKey, 'hex');
116
+ }
117
+
118
+ return new Address(Buffer.from(mldsaPublicKey, 'hex'), classicBuffer);
87
119
  }
88
120
 
89
121
  /**
@@ -117,16 +149,40 @@ export class Address extends Uint8Array {
117
149
  }
118
150
 
119
151
  /**
120
- * Converts the address to a buffer
121
- * @returns {Buffer} The buffer
152
+ * Converts the classical public key to a hex string
153
+ * @returns {string} The hex string
154
+ */
155
+ public tweakedToHex(): string {
156
+ if (!this.classicPublicKey) {
157
+ throw new Error('Classical public key not set');
158
+ }
159
+
160
+ return '0x' + Buffer.from(this.classicPublicKey).toString('hex');
161
+ }
162
+
163
+ /**
164
+ * Converts the address content (SHA256 hash of ML-DSA public key) to a buffer
165
+ * @returns {Buffer} The buffer containing the hashed ML-DSA public key
122
166
  */
123
167
  public toBuffer(): Buffer {
124
168
  return Buffer.from(this);
125
169
  }
126
170
 
171
+ /**
172
+ * Converts the classical public key to a buffer
173
+ * @returns {Buffer} The buffer
174
+ */
175
+ public tweakedPublicKeyToBuffer(): Buffer {
176
+ if (!this.classicPublicKey) {
177
+ throw new Error('Classical public key not set');
178
+ }
179
+
180
+ return Buffer.from(this.classicPublicKey);
181
+ }
182
+
127
183
  public toUncompressedHex(): string {
128
184
  if (!this.#uncompressed) {
129
- throw new Error('Public key not set');
185
+ throw new Error('Classical public key not set');
130
186
  }
131
187
 
132
188
  return '0x' + this.#uncompressed.uncompressed.toString('hex');
@@ -134,7 +190,7 @@ export class Address extends Uint8Array {
134
190
 
135
191
  public toUncompressedBuffer(): Buffer {
136
192
  if (!this.#uncompressed) {
137
- throw new Error('Public key not set');
193
+ throw new Error('Classical public key not set');
138
194
  }
139
195
 
140
196
  return this.#uncompressed.uncompressed;
@@ -142,7 +198,7 @@ export class Address extends Uint8Array {
142
198
 
143
199
  public toHybridPublicKeyHex(): string {
144
200
  if (!this.#uncompressed) {
145
- throw new Error('Public key not set');
201
+ throw new Error('Classical public key not set');
146
202
  }
147
203
 
148
204
  return '0x' + this.#uncompressed.hybrid.toString('hex');
@@ -150,7 +206,7 @@ export class Address extends Uint8Array {
150
206
 
151
207
  public toHybridPublicKeyBuffer(): Buffer {
152
208
  if (!this.#uncompressed) {
153
- throw new Error('Public key not set');
209
+ throw new Error('Classical public key not set');
154
210
  }
155
211
 
156
212
  return this.#uncompressed.hybrid;
@@ -158,7 +214,7 @@ export class Address extends Uint8Array {
158
214
 
159
215
  public originalPublicKeyBuffer(): Buffer {
160
216
  if (!this.#originalPublicKey) {
161
- throw new Error('Public key not set');
217
+ throw new Error('Classical public key not set');
162
218
  }
163
219
 
164
220
  return Buffer.from(this.#originalPublicKey);
@@ -225,24 +281,56 @@ export class Address extends Uint8Array {
225
281
 
226
282
  /**
227
283
  * Set the public key
228
- * @param {ArrayLike<number>} publicKey The public key
284
+ * @param {ArrayLike<number>} mldsaPublicKey ML-DSA public key
229
285
  * @returns {void}
230
286
  */
231
- public override set(publicKey: ArrayLike<number>): void {
232
- const validLengths = [ADDRESS_BYTE_LENGTH, 33, 65];
233
- if (!validLengths.includes(publicKey.length)) {
234
- throw new Error(`Invalid public key length ${publicKey.length}`);
235
- }
287
+ public override set(mldsaPublicKey: ArrayLike<number>): void {
288
+ if (this.classicPublicKey) {
289
+ const validLengths = [ADDRESS_BYTE_LENGTH, 33, 65];
290
+ if (!validLengths.includes(this.classicPublicKey.length)) {
291
+ throw new Error(`Invalid public key length ${this.classicPublicKey.length}`);
292
+ }
236
293
 
237
- if (publicKey.length === ADDRESS_BYTE_LENGTH) {
238
- const buf = Buffer.alloc(ADDRESS_BYTE_LENGTH);
239
- buf.set(publicKey);
294
+ if (this.classicPublicKey.length === ADDRESS_BYTE_LENGTH) {
295
+ const buf = Buffer.alloc(ADDRESS_BYTE_LENGTH);
296
+ buf.set(this.classicPublicKey);
240
297
 
241
- this.#tweakedUncompressed = ContractAddress.generateHybridKeyFromHash(buf);
298
+ this.#tweakedUncompressed = ContractAddress.generateHybridKeyFromHash(buf);
299
+ } else {
300
+ this.autoFormat(this.classicPublicKey);
301
+ }
302
+ }
242
303
 
243
- super.set(publicKey);
304
+ // THIS is the SHA256(ORIGINAL_ML_DSA_PUBLIC_KEY)
305
+ if (mldsaPublicKey.length === ADDRESS_BYTE_LENGTH) {
306
+ const buf = new Uint8Array(ADDRESS_BYTE_LENGTH);
307
+ buf.set(mldsaPublicKey);
308
+
309
+ super.set(buf);
244
310
  } else {
245
- this.autoFormat(publicKey);
311
+ // Validate ML-DSA public key lengths according to BIP360 and FIPS 204
312
+ // ML-DSA-44 (Level 2): 1312 bytes public key
313
+ // ML-DSA-65 (Level 3): 1952 bytes public key
314
+ // ML-DSA-87 (Level 5): 2592 bytes public key
315
+ const validMLDSALengths = [1312, 1952, 2592];
316
+
317
+ if (!validMLDSALengths.includes(mldsaPublicKey.length)) {
318
+ throw new Error(
319
+ `Invalid ML-DSA public key length: ${mldsaPublicKey.length}. ` +
320
+ `Expected 1312 (ML-DSA-44/LEVEL2), 1952 (ML-DSA-65/LEVEL3), or 2592 (ML-DSA-87/LEVEL5) bytes.`,
321
+ );
322
+ }
323
+
324
+ // Store the original ML-DSA public key
325
+ this.#mldsaPublicKey = new Uint8Array(mldsaPublicKey.length);
326
+ this.#mldsaPublicKey.set(mldsaPublicKey);
327
+
328
+ // Hash the ML-DSA public key to get the 32-byte address
329
+ const hashedPublicKey = sha256(new Uint8Array(mldsaPublicKey));
330
+ const buf = new Uint8Array(ADDRESS_BYTE_LENGTH);
331
+ buf.set(hashedPublicKey);
332
+
333
+ super.set(buf);
246
334
  }
247
335
  }
248
336
 
@@ -305,11 +393,18 @@ export class Address extends Uint8Array {
305
393
  * @param {Network} network The network
306
394
  */
307
395
  public p2tr(network: Network): string {
396
+ if (!this.classicPublicKey) {
397
+ throw new Error('Classical public key not set');
398
+ }
399
+
308
400
  if (this.#p2tr && this.#network === network) {
309
401
  return this.#p2tr;
310
402
  }
311
403
 
312
- const p2trAddy: string | undefined = EcKeyPair.tweakedPubKeyBufferToAddress(this, network);
404
+ const p2trAddy: string | undefined = EcKeyPair.tweakedPubKeyBufferToAddress(
405
+ this.classicPublicKey,
406
+ network,
407
+ );
313
408
 
314
409
  if (p2trAddy) {
315
410
  this.#network = network;
@@ -318,7 +413,7 @@ export class Address extends Uint8Array {
318
413
  return p2trAddy;
319
414
  }
320
415
 
321
- throw new Error('Public key not set');
416
+ throw new Error('Classical public key not set');
322
417
  }
323
418
 
324
419
  /**
@@ -407,8 +502,14 @@ export class Address extends Uint8Array {
407
502
  }
408
503
 
409
504
  /**
410
- * Get an opnet address encoded in bech32m format.
411
- * @param network
505
+ * Returns the OPNet address encoded in bech32m format, derived from the SHA256 hash of the ML-DSA public key
506
+ * (which is what the Address internally stores).
507
+ *
508
+ * This method generates a P2OP (Pay-to-OPNet) address using witness version 16, suitable for
509
+ * quantum-resistant transactions on the OPNet protocol.
510
+ *
511
+ * @param network - The Bitcoin network to use (mainnet, testnet, regtest)
512
+ * @returns The P2OP address in bech32m format
412
513
  */
413
514
  public p2op(network: Network): string {
414
515
  if (this.#p2op && this.#network === network) {
@@ -423,12 +524,12 @@ export class Address extends Uint8Array {
423
524
  return p2opAddy;
424
525
  }
425
526
 
426
- throw new Error('Public key not set');
527
+ throw new Error('ML-DSA public key not set');
427
528
  }
428
529
 
429
530
  public toTweakedHybridPublicKeyHex(): string {
430
531
  if (!this.#tweakedUncompressed) {
431
- throw new Error('Public key not set');
532
+ throw new Error('Classical public key not set');
432
533
  }
433
534
 
434
535
  return '0x' + this.#tweakedUncompressed.toString('hex');
@@ -436,7 +537,7 @@ export class Address extends Uint8Array {
436
537
 
437
538
  public toTweakedHybridPublicKeyBuffer(): Buffer {
438
539
  if (!this.#tweakedUncompressed) {
439
- throw new Error('Public key not set');
540
+ throw new Error('Classical public key not set');
440
541
  }
441
542
 
442
543
  return this.#tweakedUncompressed;
@@ -462,6 +563,7 @@ export class Address extends Uint8Array {
462
563
 
463
564
  this.#tweakedUncompressed = ContractAddress.generateHybridKeyFromHash(tweakedBytes);
464
565
 
465
- super.set(tweakedBytes);
566
+ this.classicPublicKey = new Uint8Array(ADDRESS_BYTE_LENGTH);
567
+ this.classicPublicKey.set(tweakedBytes);
466
568
  }
467
569
  }
@@ -3,6 +3,7 @@ import * as ecc from '@bitcoinerlab/secp256k1';
3
3
  import { EcKeyPair } from './EcKeyPair.js';
4
4
  import { BitcoinUtils } from '../utils/BitcoinUtils.js';
5
5
  import { P2WDADetector } from '../p2wda/P2WDADetector.js';
6
+ import { MLDSASecurityLevel } from '@btc-vision/bip32';
6
7
 
7
8
  initEccLib(ecc);
8
9
 
@@ -155,6 +156,85 @@ export class AddressVerificator {
155
156
  return false; // Not a valid public key
156
157
  }
157
158
 
159
+ /**
160
+ * Checks if the input is a valid ML-DSA public key.
161
+ * ML-DSA public keys have specific lengths depending on the security level:
162
+ * - ML-DSA-44 (Level 2): 1312 bytes (2624 hex characters)
163
+ * - ML-DSA-65 (Level 3): 1952 bytes (3904 hex characters)
164
+ * - ML-DSA-87 (Level 5): 2592 bytes (5184 hex characters)
165
+ *
166
+ * @param input - The input string (hex format) or Buffer to check.
167
+ * @returns - The security level if valid, null otherwise.
168
+ */
169
+ public static isValidMLDSAPublicKey(
170
+ input: string | Buffer | Uint8Array,
171
+ ): MLDSASecurityLevel | null {
172
+ try {
173
+ let byteLength: number;
174
+
175
+ if (Buffer.isBuffer(input) || input instanceof Uint8Array) {
176
+ byteLength = input.length;
177
+ } else {
178
+ // Handle string input
179
+ if (input.startsWith('0x')) {
180
+ input = input.slice(2);
181
+ }
182
+
183
+ if (!BitcoinUtils.isValidHex(input)) {
184
+ return null;
185
+ }
186
+
187
+ byteLength = input.length / 2;
188
+ }
189
+
190
+ // Check against valid ML-DSA public key lengths
191
+ switch (byteLength) {
192
+ case 1312:
193
+ return MLDSASecurityLevel.LEVEL2; // ML-DSA-44
194
+ case 1952:
195
+ return MLDSASecurityLevel.LEVEL3; // ML-DSA-65
196
+ case 2592:
197
+ return MLDSASecurityLevel.LEVEL5; // ML-DSA-87
198
+ default:
199
+ return null;
200
+ }
201
+ } catch (e) {
202
+ return null;
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Checks if the given address is a valid P2OP (OPNet) address.
208
+ * P2OP addresses use witness version 16 and are encoded in Bech32m format.
209
+ *
210
+ * @param inAddress - The address to check.
211
+ * @param network - The network to validate against.
212
+ * @returns - True if the address is a valid P2OP address, false otherwise.
213
+ */
214
+ public static isValidP2OPAddress(inAddress: string, network: Network): boolean {
215
+ if (!inAddress || inAddress.length < 20) return false;
216
+
217
+ try {
218
+ // Decode the Bech32/Bech32m address
219
+ const decodedAddress = address.fromBech32(inAddress);
220
+
221
+ // Check if it matches the network's bech32 or bech32Opnet prefix
222
+ const validPrefix =
223
+ decodedAddress.prefix === network.bech32 ||
224
+ decodedAddress.prefix === network.bech32Opnet;
225
+
226
+ if (!validPrefix) {
227
+ return false;
228
+ }
229
+
230
+ // P2OP uses witness version 16 (OP_16)
231
+ // The data length should be 21 bytes (1 byte deployment version + 20 byte hash160)
232
+ return decodedAddress.version === 16 && decodedAddress.data.length === 21;
233
+ } catch {
234
+ return false;
235
+ }
236
+ }
237
+
158
238
  /**
159
239
  * Checks if the address requires a redeem script to spend funds.
160
240
  * @param {string} addy - The address to check.
@@ -183,7 +263,9 @@ export class AddressVerificator {
183
263
  * - P2SH-P2WPKH (Wrapped SegWit)
184
264
  * - P2PK (Pay to PubKey, technically treated similarly to P2PKH)
185
265
  * - P2WPKH (SegWit address starting with 'bc1q' for mainnet or 'tb1q' for testnet)
266
+ * - P2WSH (SegWit script hash address)
186
267
  * - P2TR (Taproot address starting with 'bc1p' for mainnet or 'tb1p' for testnet)
268
+ * - P2OP (OPNet contract address with witness version 16)
187
269
  *
188
270
  * @param addy - The Bitcoin address to validate.
189
271
  * @param network - The Bitcoin network to validate against (mainnet, testnet, etc.).
@@ -209,12 +291,15 @@ export class AddressVerificator {
209
291
  } catch {}
210
292
 
211
293
  try {
212
- // Try to decode as a Bech32 or Bech32m address (P2WPKH or P2TR)
294
+ // Try to decode as a Bech32 or Bech32m address (P2WPKH, P2WSH, P2TR, or P2OP)
213
295
  const decodedBech32 = address.fromBech32(addy);
296
+
297
+ // P2OP: OPNet contract addresses (version 16, 21 bytes data)
214
298
  if (
215
299
  (decodedBech32.prefix === network.bech32Opnet ||
216
300
  decodedBech32.prefix === network.bech32) &&
217
- decodedBech32.version === 16
301
+ decodedBech32.version === 16 &&
302
+ decodedBech32.data.length === 21
218
303
  ) {
219
304
  return AddressTypes.P2OP;
220
305
  }
@@ -1,5 +1,12 @@
1
1
  import * as ecc from '@bitcoinerlab/secp256k1';
2
- import bip32, { BIP32API, BIP32Factory, BIP32Interface } from 'bip32';
2
+ import bip32, {
3
+ BIP32API,
4
+ BIP32Factory,
5
+ BIP32Interface,
6
+ MLDSAKeyPair,
7
+ MLDSASecurityLevel,
8
+ QuantumBIP32Factory,
9
+ } from '@btc-vision/bip32';
3
10
  import bitcoin, {
4
11
  address,
5
12
  fromOutputScript,
@@ -17,7 +24,7 @@ import { IWallet } from './interfaces/IWallet.js';
17
24
  import { secp256k1 } from '@noble/curves/secp256k1';
18
25
  import { mod } from '@noble/curves/abstract/modular';
19
26
  import { sha256 } from '@noble/hashes/sha2';
20
- import { bytesToNumberBE, concatBytes, utf8ToBytes } from '@noble/curves/utils.js';
27
+ import { bytesToNumberBE, concatBytes, randomBytes, utf8ToBytes } from '@noble/curves/utils.js';
21
28
 
22
29
  initEccLib(ecc);
23
30
 
@@ -306,11 +313,17 @@ export class EcKeyPair {
306
313
  }
307
314
 
308
315
  /**
309
- * Generate a random wallet
310
- * @param {Network} network - The network to use
311
- * @returns {IWallet} - The generated wallet
316
+ * Generate a random wallet with both classical and quantum keys
317
+ *
318
+ * @param network - The network to use
319
+ * @param securityLevel - The ML-DSA security level for quantum keys (default: LEVEL2/44)
320
+ * @returns An object containing both classical and quantum key information
312
321
  */
313
- public static generateWallet(network: Network = networks.bitcoin): IWallet {
322
+ public static generateWallet(
323
+ network: Network = networks.bitcoin,
324
+ securityLevel: MLDSASecurityLevel = MLDSASecurityLevel.LEVEL2,
325
+ ): IWallet {
326
+ // Generate classical keypair
314
327
  const keyPair = this.ECPair.makeRandom({
315
328
  network: network,
316
329
  });
@@ -321,10 +334,49 @@ export class EcKeyPair {
321
334
  throw new Error('Failed to generate wallet');
322
335
  }
323
336
 
337
+ // Generate random quantum keypair with network
338
+ const quantumKeyPair = this.generateQuantumKeyPair(securityLevel, network);
339
+
324
340
  return {
325
341
  address: wallet,
326
342
  privateKey: keyPair.toWIF(),
327
343
  publicKey: Buffer.from(keyPair.publicKey).toString('hex'),
344
+ quantumPrivateKey: Buffer.from(quantumKeyPair.privateKey).toString('hex'),
345
+ quantumPublicKey: Buffer.from(quantumKeyPair.publicKey).toString('hex'),
346
+ };
347
+ }
348
+
349
+ /**
350
+ * Generate a random quantum ML-DSA keypair
351
+ *
352
+ * This creates a standalone quantum-resistant keypair without using BIP32 derivation.
353
+ * The keys are generated using cryptographically secure random bytes.
354
+ *
355
+ * @param securityLevel - The ML-DSA security level (default: LEVEL2/44)
356
+ * @param network - The Bitcoin network (default: bitcoin mainnet)
357
+ * @returns A random ML-DSA keypair
358
+ */
359
+ public static generateQuantumKeyPair(
360
+ securityLevel: MLDSASecurityLevel = MLDSASecurityLevel.LEVEL2,
361
+ network: Network = networks.bitcoin,
362
+ ): MLDSAKeyPair {
363
+ // Generate random seed for quantum key generation
364
+ const randomSeed = randomBytes(64);
365
+
366
+ // Create a quantum root from the random seed with network parameter
367
+ const quantumRoot = QuantumBIP32Factory.fromSeed(
368
+ Buffer.from(randomSeed),
369
+ network,
370
+ securityLevel,
371
+ );
372
+
373
+ if (!quantumRoot.privateKey || !quantumRoot.publicKey) {
374
+ throw new Error('Failed to generate quantum keypair');
375
+ }
376
+
377
+ return {
378
+ privateKey: Buffer.from(quantumRoot.privateKey),
379
+ publicKey: Buffer.from(quantumRoot.publicKey),
328
380
  };
329
381
  }
330
382
 
@@ -3,12 +3,20 @@ import * as ecc from '@bitcoinerlab/secp256k1';
3
3
  import { crypto, Network, toXOnly } from '@btc-vision/bitcoin';
4
4
  import { TweakedSigner } from '../signer/TweakedSigner.js';
5
5
  import { EcKeyPair } from './EcKeyPair.js';
6
+ import { MLDSASecurityLevel, QuantumBIP32Interface } from '@btc-vision/bip32';
6
7
 
7
8
  export interface SignedMessage {
8
9
  readonly signature: Uint8Array;
9
10
  readonly message: Uint8Array;
10
11
  }
11
12
 
13
+ export interface MLDSASignedMessage {
14
+ readonly signature: Uint8Array;
15
+ readonly message: Uint8Array;
16
+ readonly publicKey: Uint8Array;
17
+ readonly securityLevel: MLDSASecurityLevel;
18
+ }
19
+
12
20
  class MessageSignerBase {
13
21
  public sha256(message: Buffer | Uint8Array): Buffer {
14
22
  return crypto.sha256(Buffer.from(message));
@@ -101,6 +109,56 @@ class MessageSignerBase {
101
109
 
102
110
  return this.verifySignature(tweakedPublicKey, message, signature);
103
111
  }
112
+
113
+ /**
114
+ * Signs a message using ML-DSA signature scheme.
115
+ * @param {QuantumBIP32Interface} mldsaKeypair - The ML-DSA keypair to sign with. Must contain a private key.
116
+ * @param {Uint8Array | Buffer | string} message - The message to sign.
117
+ * @returns The ML-DSA signature with metadata.
118
+ * @throws Error if the private key is missing.
119
+ */
120
+ public signMLDSAMessage(
121
+ mldsaKeypair: QuantumBIP32Interface,
122
+ message: Uint8Array | Buffer | string,
123
+ ): MLDSASignedMessage {
124
+ if (typeof message === 'string') {
125
+ message = Buffer.from(message, 'utf-8');
126
+ }
127
+
128
+ if (!mldsaKeypair.privateKey) {
129
+ throw new Error('ML-DSA private key not found in keypair.');
130
+ }
131
+
132
+ const hashedMessage = this.sha256(message);
133
+ const signature = mldsaKeypair.sign(hashedMessage);
134
+
135
+ return {
136
+ signature: Buffer.from(signature),
137
+ message: hashedMessage,
138
+ publicKey: Buffer.from(mldsaKeypair.publicKey),
139
+ securityLevel: mldsaKeypair.securityLevel,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Verifies an ML-DSA signature using the provided keypair.
145
+ * @param {QuantumBIP32Interface} mldsaKeypair - The ML-DSA keypair with the public key.
146
+ * @param {Uint8Array | Buffer | string} message - The message to verify.
147
+ * @param {Uint8Array | Buffer} signature - The ML-DSA signature to verify.
148
+ * @returns True if the signature is valid, false otherwise.
149
+ */
150
+ public verifyMLDSASignature(
151
+ mldsaKeypair: QuantumBIP32Interface,
152
+ message: Uint8Array | Buffer | string,
153
+ signature: Uint8Array | Buffer,
154
+ ): boolean {
155
+ if (typeof message === 'string') {
156
+ message = Buffer.from(message, 'utf-8');
157
+ }
158
+
159
+ const hashedMessage = this.sha256(message);
160
+ return mldsaKeypair.verify(hashedMessage, signature);
161
+ }
104
162
  }
105
163
 
106
164
  export const MessageSigner = new MessageSignerBase();