@neuraiproject/neurai-key 3.0.0 → 3.0.2

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/README.md CHANGED
@@ -16,7 +16,7 @@ That is, use your 12 words to get addresses for Neurai mainnet and testnet.
16
16
  - ✅ Mainnet and Testnet support for Neurai (XNA)
17
17
  - ✅ Support for both XNA (BIP44: 1900) and XNA Legacy (BIP44: 0) networks
18
18
  - ✅ Convert raw public keys into Neurai mainnet or testnet addresses
19
- - ✅ PostQuantum addresses using ML-DSA-44 (FIPS 204) with Bech32m encoding
19
+ - ✅ PostQuantum AuthScript addresses using ML-DSA-44 (FIPS 204) with Bech32m encoding
20
20
 
21
21
  ## Network Types
22
22
 
@@ -24,12 +24,12 @@ This library supports three Neurai network configurations:
24
24
 
25
25
  - **`xna` / `xna-test`**: Current Neurai standard (BIP44 coin type: 1900)
26
26
  - **`xna-legacy` / `xna-legacy-test`**: Legacy Neurai addresses (BIP44 coin type: 0)
27
- - **`xna-pq` / `xna-pq-test`**: PostQuantum ML-DSA-44 addresses (Bech32m, witness v1)
27
+ - **`xna-pq` / `xna-pq-test`**: PostQuantum ML-DSA-44 AuthScript addresses (Bech32m, witness v1)
28
28
 
29
29
  The main difference is the derivation path and address encoding:
30
- - **XNA**: `m/44'/1900'/0'/0/0` — Base58Check, prefix `N` (recommended for new wallets)
31
- - **XNA Legacy**: `m/44'/0'/0'/0/0` — Base58Check, prefix `N` (for compatibility with older wallets)
32
- - **XNA PostQuantum**: `m/100'/1900'/0'/0/0` — Bech32m, prefix `nq1` (for future quantum-resistant fork)
30
+ - **XNA**: mainnet `m/44'/1900'/0'/0/0`, testnet `m/44'/1'/0'/0/0` — Base58Check (recommended for new wallets)
31
+ - **XNA Legacy**: mainnet `m/44'/0'/0'/0/0`, testnet `m/44'/1'/0'/0/0` — Base58Check (for compatibility with older wallets)
32
+ - **XNA PostQuantum**: mainnet `m/100'/1900'/0'/0/0`, testnet default/external `m/100'/1'/0'/0/0` — Bech32m (`nq1` / `tnq1`)
33
33
 
34
34
  **Note**: Using different network types will generate completely different addresses from the same mnemonic.
35
35
 
@@ -181,9 +181,9 @@ console.log(testAddress); // tPXGaMRNwZuV1UKSrD9gABPscrJWUmedQ9
181
181
 
182
182
  `publicKeyToAddress` throws if the key length is not 33 or 65 bytes so invalid inputs are surfaced immediately.
183
183
 
184
- ## PostQuantum (ML-DSA-44) Addresses
184
+ ## PostQuantum (ML-DSA-44) AuthScript Addresses
185
185
 
186
- Generate quantum-resistant addresses using the ML-DSA-44 signature scheme (FIPS 204). These addresses use Bech32m encoding with witness version 1, preparing for a future post-quantum fork.
186
+ Generate quantum-resistant AuthScript addresses using the ML-DSA-44 signature scheme (FIPS 204). The library now follows the migrated `witness v1 = AuthScript` layout, so the Bech32m program is a 32-byte commitment instead of the old 20-byte PQ keyhash.
187
187
 
188
188
  ### Generate a PQ address
189
189
 
@@ -199,15 +199,21 @@ const pqAddress = NeuraiKey.getPQAddress(network, mnemonic, ACCOUNT, INDEX);
199
199
  console.log(pqAddress);
200
200
  ```
201
201
 
202
+ `getPQAddress()` returns the external branch by default.
203
+
202
204
  Outputs
203
205
 
204
206
  ```
205
207
  {
206
- address: 'nq1...', // Bech32m address
208
+ address: 'nq1...', // Bech32m AuthScript address
209
+ authType: 1, // 0x01 = PQ single-key auth
210
+ authDescriptor: '01...', // 0x01 || HASH160(pq_pubkey)
211
+ commitment: '...', // tagged_hash("NeuraiAuthScript", ...)
207
212
  path: "m/100'/1900'/0'/0/0", // PQ derivation path
208
213
  publicKey: '...', // ML-DSA-44 public key (2624 hex chars = 1312 bytes)
209
214
  privateKey: '...', // ML-DSA-44 private key (5120 hex chars = 2560 bytes)
210
- seedKey: '...' // 32-byte BIP32 seed used for ML-DSA keygen (64 hex chars)
215
+ seedKey: '...', // 32-byte BIP32 seed used for ML-DSA keygen (64 hex chars)
216
+ witnessScript: '51' // default OP_TRUE script for simple PQ auth
211
217
  }
212
218
  ```
213
219
 
@@ -227,6 +233,19 @@ const reconstructed = NeuraiKey.pqPublicKeyToAddress("xna-pq", pqAddress.publicK
227
233
  // reconstructed === pqAddress.address
228
234
  ```
229
235
 
236
+ ### Custom AuthScript witnessScript
237
+
238
+ ```javascript
239
+ const pqAddress = NeuraiKey.getPQAddress("xna-pq", mnemonic, 0, 0, "", {
240
+ witnessScript: "5151"
241
+ });
242
+
243
+ console.log(pqAddress.witnessScript); // 5151
244
+ console.log(pqAddress.commitment); // new 32-byte commitment
245
+ ```
246
+
247
+ `authType` no es configurable en la API pública. Esta librería genera únicamente direcciones PQ simples con `authType = 0x01`.
248
+
230
249
  ### Advanced: derive by path with HD key reuse
231
250
 
232
251
  ```javascript
@@ -235,20 +254,22 @@ const addr0 = NeuraiKey.getPQAddressByPath("xna-pq", hdKey, "m/100'/1900'/0'/0/0
235
254
  const addr1 = NeuraiKey.getPQAddressByPath("xna-pq", hdKey, "m/100'/1900'/0'/0/1");
236
255
  ```
237
256
 
238
- ### PQ Address Details
257
+ ### PQ AuthScript Details
239
258
 
240
259
  | Property | Value |
241
260
  |----------|-------|
242
261
  | Signature algorithm | ML-DSA-44 (FIPS 204) |
243
- | Address encoding | Bech32m (witness version 1) |
262
+ | Address encoding | Bech32m (`witness v1` AuthScript) |
244
263
  | Mainnet HRP / prefix | `nq` / `nq1...` |
245
264
  | Testnet HRP / prefix | `tnq` / `tnq1...` |
246
265
  | Public key size | 1312 bytes |
247
266
  | Derivation path (mainnet) | `m/100'/1900'/0'/0/index` |
248
- | Derivation path (testnet) | `m/100'/1900'/0'/1/index` |
249
- | Address hash | HASH160(0x05 \|\| pubkey) |
267
+ | Derivation path (testnet default/external) | `m/100'/1'/account'/0/index` |
268
+ | Auth descriptor | `0x01 \|\| HASH160(pq_pubkey)` |
269
+ | Commitment | `tagged_hash("NeuraiAuthScript", 0x01 \|\| auth_descriptor \|\| SHA256(witnessScript))` |
270
+ | Default witnessScript | `OP_TRUE` (`51` in hex) |
250
271
 
251
- **Note**: PQ addresses do not have a WIF (Wallet Import Format) field since WIF is specific to secp256k1 keys. The `seedKey` field contains the 32-byte BIP32-derived seed used for deterministic ML-DSA-44 key generation, useful for cross-implementation verification.
272
+ **Note**: PQ AuthScript addresses do not have a WIF (Wallet Import Format) field since WIF is specific to secp256k1 keys. The `seedKey` field contains the 32-byte BIP32-derived seed used for deterministic ML-DSA-44 key generation, useful for cross-implementation verification.
252
273
 
253
274
  ## Get public key from WIF
254
275
 
@@ -21503,6 +21503,13 @@ zurdo`.split('\n');
21503
21503
  function hash160(data) {
21504
21504
  return ripemd160(sha256(data));
21505
21505
  }
21506
+ function sha256Hash(data) {
21507
+ return sha256(data);
21508
+ }
21509
+ function taggedHash(tag, data) {
21510
+ const tagHash = sha256(utf8ToBytes(tag));
21511
+ return sha256(concatBytes(tagHash, tagHash, data));
21512
+ }
21506
21513
  function doubleSha256(data) {
21507
21514
  return sha256(sha256(data));
21508
21515
  }
@@ -21543,7 +21550,6 @@ zurdo`.split('\n');
21543
21550
  return Uint8Array.from(mnemonicToSeedSync(mnemonic, passphrase));
21544
21551
  }
21545
21552
  const BITCOIN_SEED_KEY = utf8ToBytes("Bitcoin seed");
21546
- const HASH160_PREFIX = Uint8Array.from([0x05]);
21547
21553
 
21548
21554
  /**
21549
21555
  * Utils for modular division and fields.
@@ -23708,6 +23714,10 @@ zurdo`.split('\n');
23708
23714
 
23709
23715
  var distExports = requireDist();
23710
23716
 
23717
+ const AUTHSCRIPT_TAG = "NeuraiAuthScript";
23718
+ const AUTHSCRIPT_VERSION = 0x01;
23719
+ const PQ_AUTH_TYPE = 0x01;
23720
+ const DEFAULT_WITNESS_SCRIPT = Uint8Array.from([0x51]);
23711
23721
  function encodeWIF(privateKey, version, compressed = true) {
23712
23722
  const payload = compressed
23713
23723
  ? concatBytes(Uint8Array.from([version]), privateKey, Uint8Array.from([0x01]))
@@ -23764,9 +23774,29 @@ zurdo`.split('\n');
23764
23774
  function bech32mEncode(hrp, witnessVersion, hash) {
23765
23775
  return distExports.bech32m.encode(hrp, [witnessVersion, ...distExports.bech32m.toWords(hash)]);
23766
23776
  }
23767
- function pqPublicKeyToAddressBytes(publicKey, network) {
23768
- const serialized = concatBytes(HASH160_PREFIX, publicKey);
23769
- return bech32mEncode(network.hrp, network.witnessVersion, hash160(serialized));
23777
+ function normalizeWitnessScript(input) {
23778
+ return input ? ensureBytes(input) : Uint8Array.from(DEFAULT_WITNESS_SCRIPT);
23779
+ }
23780
+ function pqPublicKeyToAuthDescriptor(publicKey) {
23781
+ return concatBytes(Uint8Array.from([PQ_AUTH_TYPE]), hash160(publicKey));
23782
+ }
23783
+ function pqPublicKeyToCommitment(publicKey, options = {}) {
23784
+ return pqPublicKeyToCommitmentParts(publicKey, options).commitment;
23785
+ }
23786
+ function pqPublicKeyToCommitmentParts(publicKey, options = {}) {
23787
+ const witnessScript = normalizeWitnessScript(options.witnessScript);
23788
+ const authDescriptor = pqPublicKeyToAuthDescriptor(publicKey);
23789
+ const witnessScriptHash = sha256Hash(witnessScript);
23790
+ const commitment = taggedHash(AUTHSCRIPT_TAG, concatBytes(Uint8Array.from([AUTHSCRIPT_VERSION]), authDescriptor, witnessScriptHash));
23791
+ return {
23792
+ authDescriptor,
23793
+ authType: PQ_AUTH_TYPE,
23794
+ commitment,
23795
+ witnessScript,
23796
+ };
23797
+ }
23798
+ function pqPublicKeyToAddressBytes(publicKey, network, options = {}) {
23799
+ return bech32mEncode(network.hrp, network.witnessVersion, pqPublicKeyToCommitment(publicKey, options));
23770
23800
  }
23771
23801
  function normalizePublicKey(input) {
23772
23802
  return ensureBytes(input);
@@ -23928,8 +23958,8 @@ zurdo`.split('\n');
23928
23958
  hrp: "tnq",
23929
23959
  witnessVersion: 1,
23930
23960
  purpose: 100,
23931
- coinType: 1900,
23932
- changeIndex: 1,
23961
+ coinType: 1,
23962
+ changeIndex: 0,
23933
23963
  bip32: { private: 70615956, public: 70617039 },
23934
23964
  },
23935
23965
  };
@@ -24026,7 +24056,7 @@ zurdo`.split('\n');
24026
24056
  const seed = mnemonicToSeedBytes(mnemonicToSeedSync, mnemonic, passphrase);
24027
24057
  return HDKey.fromMasterSeed(seed, chain.bip32);
24028
24058
  }
24029
- function getPQAddressByPath(network, hdKey, path) {
24059
+ function getPQAddressByPath(network, hdKey, path, options = {}) {
24030
24060
  const chain = getPQNetwork(network);
24031
24061
  const derived = hdKey.derive(path);
24032
24062
  if (!derived.privateKey) {
@@ -24034,30 +24064,50 @@ zurdo`.split('\n');
24034
24064
  }
24035
24065
  const seed32 = Uint8Array.from(derived.privateKey);
24036
24066
  const { publicKey, secretKey } = ml_dsa44.keygen(seed32);
24067
+ const authScript = pqPublicKeyToCommitmentParts(publicKey, options);
24037
24068
  return {
24038
- address: pqPublicKeyToAddressBytes(publicKey, chain),
24069
+ address: pqPublicKeyToAddressBytes(publicKey, chain, options),
24070
+ authType: authScript.authType,
24071
+ authDescriptor: bytesToHex(authScript.authDescriptor),
24072
+ commitment: bytesToHex(authScript.commitment),
24039
24073
  path,
24040
24074
  publicKey: bytesToHex(publicKey),
24041
24075
  privateKey: bytesToHex(secretKey),
24042
24076
  seedKey: bytesToHex(seed32),
24077
+ witnessScript: bytesToHex(authScript.witnessScript),
24043
24078
  };
24044
24079
  }
24045
- function getPQAddress(network, mnemonic, account, index, passphrase = "") {
24080
+ function getPQAddress(network, mnemonic, account, index, passphrase = "", options = {}) {
24046
24081
  const chain = getPQNetwork(network);
24047
24082
  const hdKey = getPQHDKey(network, mnemonic, passphrase);
24048
24083
  const path = `m/${chain.purpose}'/${chain.coinType}'/${account}'/${chain.changeIndex}/${index}`;
24049
- return getPQAddressByPath(network, hdKey, path);
24084
+ return getPQAddressByPath(network, hdKey, path, options);
24085
+ }
24086
+ function pqPublicKeyToAddress(network, publicKey, options = {}) {
24087
+ const keyBytes = ensureBytes(publicKey);
24088
+ if (keyBytes.length !== 1312) {
24089
+ throw new Error("ML-DSA-44 public key must be 1312 bytes");
24090
+ }
24091
+ normalizeWitnessScript(options.witnessScript);
24092
+ return pqPublicKeyToAddressBytes(keyBytes, getPQNetwork(network), options);
24093
+ }
24094
+ function pqPublicKeyToCommitmentHex(publicKey, options = {}) {
24095
+ const keyBytes = ensureBytes(publicKey);
24096
+ if (keyBytes.length !== 1312) {
24097
+ throw new Error("ML-DSA-44 public key must be 1312 bytes");
24098
+ }
24099
+ return bytesToHex(pqPublicKeyToCommitment(keyBytes, options));
24050
24100
  }
24051
- function pqPublicKeyToAddress(network, publicKey) {
24101
+ function pqPublicKeyToAuthDescriptorHex(publicKey) {
24052
24102
  const keyBytes = ensureBytes(publicKey);
24053
24103
  if (keyBytes.length !== 1312) {
24054
24104
  throw new Error("ML-DSA-44 public key must be 1312 bytes");
24055
24105
  }
24056
- return pqPublicKeyToAddressBytes(keyBytes, getPQNetwork(network));
24106
+ return bytesToHex(pqPublicKeyToAuthDescriptor(keyBytes));
24057
24107
  }
24058
- function generatePQAddressObject(network = "xna-pq", passphrase = "") {
24108
+ function generatePQAddressObject(network = "xna-pq", passphrase = "", options = {}) {
24059
24109
  const mnemonic = generateMnemonic();
24060
- const addressObj = getPQAddress(network, mnemonic, 0, 0, passphrase);
24110
+ const addressObj = getPQAddress(network, mnemonic, 0, 0, passphrase, options);
24061
24111
  return {
24062
24112
  ...addressObj,
24063
24113
  mnemonic,
@@ -24080,6 +24130,8 @@ zurdo`.split('\n');
24080
24130
  getPQAddressByPath,
24081
24131
  getPQHDKey,
24082
24132
  pqPublicKeyToAddress,
24133
+ pqPublicKeyToAuthDescriptorHex,
24134
+ pqPublicKeyToCommitmentHex,
24083
24135
  generatePQAddressObject,
24084
24136
  };
24085
24137