@neuraiproject/neurai-message 0.9.0 → 0.9.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Sign and verify messages in Neurai in JavaScript for Node.js and modern browsers
4
4
 
5
5
  ## Scope
6
6
 
7
- This package follows the current Neurai `signmessage` / `verifymessage` behavior for `legacy` signatures and also exposes the new `PQ` message-signature format.
7
+ This package follows the current Neurai `signmessage` / `verifymessage` behavior for `legacy` signatures and also exposes the current `PQ` message-signature format bound to `AuthScript` witness-v1 addresses.
8
8
 
9
9
  The package supports two formats:
10
10
 
@@ -25,13 +25,16 @@ Instead, the exported signature embeds the serialized public key and the `ML-DSA
25
25
 
26
26
  - decode the Base64 payload
27
27
  - extract the serialized PQ public key
28
- - confirm `HASH160(pubkey_serialized)` matches the witness v1 program in the address
28
+ - derive the default `AuthScript` commitment for `auth_type=0x01` and `witnessScript=OP_TRUE`
29
+ - confirm that 32-byte commitment matches the witness v1 program in the address
29
30
  - verify the `ML-DSA-44` signature over the Neurai message hash
30
31
 
31
32
  The generic `verifyMessage(...)` function now auto-detects both formats. Use `sign(...)` for legacy and `signPQMessage(...)` for PQ.
32
33
 
33
34
  `signPQMessage(...)` expects the ML-DSA-44 secret key and the corresponding public key, either raw (`1312` bytes) or serialized as `0x05 || pubkey`.
34
35
 
36
+ Legacy PQ witness-v1 keyhash addresses (`OP_1 <20-byte-hash>`) are intentionally not supported anymore. The package now matches the current Neurai `AuthScript` destination model (`OP_1 <32-byte-commitment>`).
37
+
35
38
  ## Package outputs
36
39
 
37
40
  This package now publishes explicit entry points:
@@ -88,12 +91,25 @@ const CoinKey = require("coinkey");
88
91
  const crypto = require("crypto");
89
92
  const { signPQMessage, verifyPQMessage } = require("@neuraiproject/neurai-message");
90
93
 
94
+ function taggedHash(tag, bytes) {
95
+ const tagHash = crypto.createHash("sha256").update(tag).digest();
96
+ return crypto.createHash("sha256").update(Buffer.concat([tagHash, tagHash, bytes])).digest();
97
+ }
98
+
91
99
  const keys = ml_dsa44.keygen();
92
100
  const serializedPubKey = Buffer.concat([Buffer.from([0x05]), Buffer.from(keys.publicKey)]);
93
- const program = crypto.createHash("ripemd160").update(
94
- crypto.createHash("sha256").update(serializedPubKey).digest()
95
- ).digest();
96
- const words = bech32m.toWords(program);
101
+ const authDescriptor = Buffer.concat([
102
+ Buffer.from([0x01]),
103
+ crypto.createHash("ripemd160").update(
104
+ crypto.createHash("sha256").update(serializedPubKey).digest()
105
+ ).digest(),
106
+ ]);
107
+ const witnessScriptHash = crypto.createHash("sha256").update(Buffer.from([0x51])).digest(); // OP_TRUE
108
+ const commitment = taggedHash(
109
+ "NeuraiAuthScript",
110
+ Buffer.concat([Buffer.from([0x01]), authDescriptor, witnessScriptHash])
111
+ );
112
+ const words = bech32m.toWords(commitment);
97
113
  words.unshift(1);
98
114
  const address = bech32m.encode("tnq", words);
99
115
 
@@ -12624,6 +12624,11 @@ var NeuraiMessage = (() => {
12624
12624
  var PQ_PUBLIC_KEY_LENGTH = 1312;
12625
12625
  var PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;
12626
12626
  var PQ_SIGNATURE_LENGTH = 2420;
12627
+ var AUTHSCRIPT_PROGRAM_LENGTH = 32;
12628
+ var AUTHSCRIPT_DEFAULT_AUTH_TYPE = 1;
12629
+ var AUTHSCRIPT_DOMAIN_SEPARATOR = 1;
12630
+ var AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT = import_buffer3.Buffer.from([81]);
12631
+ var AUTHSCRIPT_TAG = "NeuraiAuthScript";
12627
12632
  var LEGACY_MESSAGE_PREFIX = String.fromCharCode(import_buffer3.Buffer.byteLength(MESSAGE_MAGIC, "utf8")) + MESSAGE_MAGIC;
12628
12633
  function encodeCompactSize(value) {
12629
12634
  if (!Number.isInteger(value) || value < 0) {
@@ -12680,6 +12685,10 @@ var NeuraiMessage = (() => {
12680
12685
  function hash1602(bytes) {
12681
12686
  return (0, import_create_hash2.default)("ripemd160").update(sha2563(bytes)).digest();
12682
12687
  }
12688
+ function taggedHash(tag, bytes) {
12689
+ const tagHash = sha2563(import_buffer3.Buffer.from(tag, "utf8"));
12690
+ return sha2563(import_buffer3.Buffer.concat([tagHash, tagHash, import_buffer3.Buffer.from(bytes)]));
12691
+ }
12683
12692
  function encodeMessageHash(message) {
12684
12693
  const messageBytes = import_buffer3.Buffer.from(message, "utf8");
12685
12694
  const magicBytes = import_buffer3.Buffer.from(MESSAGE_MAGIC, "utf8");
@@ -12719,6 +12728,19 @@ var NeuraiMessage = (() => {
12719
12728
  program: import_buffer3.Buffer.from(import_bech322.bech32m.fromWords(decoded.words.slice(1)))
12720
12729
  };
12721
12730
  }
12731
+ function getDefaultPQAuthScriptCommitment(serializedPublicKey) {
12732
+ const authDescriptor = import_buffer3.Buffer.concat([
12733
+ import_buffer3.Buffer.from([AUTHSCRIPT_DEFAULT_AUTH_TYPE]),
12734
+ hash1602(serializedPublicKey)
12735
+ ]);
12736
+ const witnessScriptHash = sha2563(AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT);
12737
+ const preimage = import_buffer3.Buffer.concat([
12738
+ import_buffer3.Buffer.from([AUTHSCRIPT_DOMAIN_SEPARATOR]),
12739
+ authDescriptor,
12740
+ witnessScriptHash
12741
+ ]);
12742
+ return taggedHash(AUTHSCRIPT_TAG, preimage);
12743
+ }
12722
12744
  function sign2(message, privateKey, compressed = true) {
12723
12745
  const signature = signLegacyMessage(
12724
12746
  message,
@@ -12778,10 +12800,12 @@ var NeuraiMessage = (() => {
12778
12800
  return false;
12779
12801
  }
12780
12802
  const decodedAddress = decodePQAddress(address);
12781
- if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {
12803
+ if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
12782
12804
  return false;
12783
12805
  }
12784
- const expectedProgram = hash1602(serializedPublicKey);
12806
+ const expectedProgram = getDefaultPQAuthScriptCommitment(
12807
+ serializedPublicKey
12808
+ );
12785
12809
  if (!expectedProgram.equals(decodedAddress.program)) {
12786
12810
  return false;
12787
12811
  }
package/dist/browser.mjs CHANGED
@@ -12598,6 +12598,11 @@ var PQ_SERIALIZED_PUBKEY_PREFIX = 5;
12598
12598
  var PQ_PUBLIC_KEY_LENGTH = 1312;
12599
12599
  var PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;
12600
12600
  var PQ_SIGNATURE_LENGTH = 2420;
12601
+ var AUTHSCRIPT_PROGRAM_LENGTH = 32;
12602
+ var AUTHSCRIPT_DEFAULT_AUTH_TYPE = 1;
12603
+ var AUTHSCRIPT_DOMAIN_SEPARATOR = 1;
12604
+ var AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT = import_buffer3.Buffer.from([81]);
12605
+ var AUTHSCRIPT_TAG = "NeuraiAuthScript";
12601
12606
  var LEGACY_MESSAGE_PREFIX = String.fromCharCode(import_buffer3.Buffer.byteLength(MESSAGE_MAGIC, "utf8")) + MESSAGE_MAGIC;
12602
12607
  function encodeCompactSize(value) {
12603
12608
  if (!Number.isInteger(value) || value < 0) {
@@ -12654,6 +12659,10 @@ function hash2562(bytes) {
12654
12659
  function hash1602(bytes) {
12655
12660
  return (0, import_create_hash2.default)("ripemd160").update(sha2563(bytes)).digest();
12656
12661
  }
12662
+ function taggedHash(tag, bytes) {
12663
+ const tagHash = sha2563(import_buffer3.Buffer.from(tag, "utf8"));
12664
+ return sha2563(import_buffer3.Buffer.concat([tagHash, tagHash, import_buffer3.Buffer.from(bytes)]));
12665
+ }
12657
12666
  function encodeMessageHash(message) {
12658
12667
  const messageBytes = import_buffer3.Buffer.from(message, "utf8");
12659
12668
  const magicBytes = import_buffer3.Buffer.from(MESSAGE_MAGIC, "utf8");
@@ -12693,6 +12702,19 @@ function decodePQAddress(address) {
12693
12702
  program: import_buffer3.Buffer.from(import_bech322.bech32m.fromWords(decoded.words.slice(1)))
12694
12703
  };
12695
12704
  }
12705
+ function getDefaultPQAuthScriptCommitment(serializedPublicKey) {
12706
+ const authDescriptor = import_buffer3.Buffer.concat([
12707
+ import_buffer3.Buffer.from([AUTHSCRIPT_DEFAULT_AUTH_TYPE]),
12708
+ hash1602(serializedPublicKey)
12709
+ ]);
12710
+ const witnessScriptHash = sha2563(AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT);
12711
+ const preimage = import_buffer3.Buffer.concat([
12712
+ import_buffer3.Buffer.from([AUTHSCRIPT_DOMAIN_SEPARATOR]),
12713
+ authDescriptor,
12714
+ witnessScriptHash
12715
+ ]);
12716
+ return taggedHash(AUTHSCRIPT_TAG, preimage);
12717
+ }
12696
12718
  function sign2(message, privateKey, compressed = true) {
12697
12719
  const signature = signLegacyMessage(
12698
12720
  message,
@@ -12752,10 +12774,12 @@ function verifyPQMessage(message, address, signature) {
12752
12774
  return false;
12753
12775
  }
12754
12776
  const decodedAddress = decodePQAddress(address);
12755
- if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {
12777
+ if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
12756
12778
  return false;
12757
12779
  }
12758
- const expectedProgram = hash1602(serializedPublicKey);
12780
+ const expectedProgram = getDefaultPQAuthScriptCommitment(
12781
+ serializedPublicKey
12782
+ );
12759
12783
  if (!expectedProgram.equals(decodedAddress.program)) {
12760
12784
  return false;
12761
12785
  }
package/dist/index.cjs CHANGED
@@ -2699,6 +2699,11 @@ var PQ_SERIALIZED_PUBKEY_PREFIX = 5;
2699
2699
  var PQ_PUBLIC_KEY_LENGTH = 1312;
2700
2700
  var PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;
2701
2701
  var PQ_SIGNATURE_LENGTH = 2420;
2702
+ var AUTHSCRIPT_PROGRAM_LENGTH = 32;
2703
+ var AUTHSCRIPT_DEFAULT_AUTH_TYPE = 1;
2704
+ var AUTHSCRIPT_DOMAIN_SEPARATOR = 1;
2705
+ var AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT = import_buffer2.Buffer.from([81]);
2706
+ var AUTHSCRIPT_TAG = "NeuraiAuthScript";
2702
2707
  var LEGACY_MESSAGE_PREFIX = String.fromCharCode(import_buffer2.Buffer.byteLength(MESSAGE_MAGIC, "utf8")) + MESSAGE_MAGIC;
2703
2708
  function encodeCompactSize(value) {
2704
2709
  if (!Number.isInteger(value) || value < 0) {
@@ -2755,6 +2760,10 @@ function hash2562(bytes) {
2755
2760
  function hash1602(bytes) {
2756
2761
  return (0, import_create_hash2.default)("ripemd160").update(sha2563(bytes)).digest();
2757
2762
  }
2763
+ function taggedHash(tag, bytes) {
2764
+ const tagHash = sha2563(import_buffer2.Buffer.from(tag, "utf8"));
2765
+ return sha2563(import_buffer2.Buffer.concat([tagHash, tagHash, import_buffer2.Buffer.from(bytes)]));
2766
+ }
2758
2767
  function encodeMessageHash(message) {
2759
2768
  const messageBytes = import_buffer2.Buffer.from(message, "utf8");
2760
2769
  const magicBytes = import_buffer2.Buffer.from(MESSAGE_MAGIC, "utf8");
@@ -2794,6 +2803,19 @@ function decodePQAddress(address) {
2794
2803
  program: import_buffer2.Buffer.from(import_bech322.bech32m.fromWords(decoded.words.slice(1)))
2795
2804
  };
2796
2805
  }
2806
+ function getDefaultPQAuthScriptCommitment(serializedPublicKey) {
2807
+ const authDescriptor = import_buffer2.Buffer.concat([
2808
+ import_buffer2.Buffer.from([AUTHSCRIPT_DEFAULT_AUTH_TYPE]),
2809
+ hash1602(serializedPublicKey)
2810
+ ]);
2811
+ const witnessScriptHash = sha2563(AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT);
2812
+ const preimage = import_buffer2.Buffer.concat([
2813
+ import_buffer2.Buffer.from([AUTHSCRIPT_DOMAIN_SEPARATOR]),
2814
+ authDescriptor,
2815
+ witnessScriptHash
2816
+ ]);
2817
+ return taggedHash(AUTHSCRIPT_TAG, preimage);
2818
+ }
2797
2819
  function sign2(message, privateKey, compressed = true) {
2798
2820
  const signature = signLegacyMessage(
2799
2821
  message,
@@ -2853,10 +2875,12 @@ function verifyPQMessage(message, address, signature) {
2853
2875
  return false;
2854
2876
  }
2855
2877
  const decodedAddress = decodePQAddress(address);
2856
- if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {
2878
+ if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
2857
2879
  return false;
2858
2880
  }
2859
- const expectedProgram = hash1602(serializedPublicKey);
2881
+ const expectedProgram = getDefaultPQAuthScriptCommitment(
2882
+ serializedPublicKey
2883
+ );
2860
2884
  if (!expectedProgram.equals(decodedAddress.program)) {
2861
2885
  return false;
2862
2886
  }
package/dist/index.mjs CHANGED
@@ -2689,6 +2689,11 @@ var PQ_SERIALIZED_PUBKEY_PREFIX = 5;
2689
2689
  var PQ_PUBLIC_KEY_LENGTH = 1312;
2690
2690
  var PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;
2691
2691
  var PQ_SIGNATURE_LENGTH = 2420;
2692
+ var AUTHSCRIPT_PROGRAM_LENGTH = 32;
2693
+ var AUTHSCRIPT_DEFAULT_AUTH_TYPE = 1;
2694
+ var AUTHSCRIPT_DOMAIN_SEPARATOR = 1;
2695
+ var AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT = Buffer3.from([81]);
2696
+ var AUTHSCRIPT_TAG = "NeuraiAuthScript";
2692
2697
  var LEGACY_MESSAGE_PREFIX = String.fromCharCode(Buffer3.byteLength(MESSAGE_MAGIC, "utf8")) + MESSAGE_MAGIC;
2693
2698
  function encodeCompactSize(value) {
2694
2699
  if (!Number.isInteger(value) || value < 0) {
@@ -2745,6 +2750,10 @@ function hash2562(bytes) {
2745
2750
  function hash1602(bytes) {
2746
2751
  return (0, import_create_hash2.default)("ripemd160").update(sha2563(bytes)).digest();
2747
2752
  }
2753
+ function taggedHash(tag, bytes) {
2754
+ const tagHash = sha2563(Buffer3.from(tag, "utf8"));
2755
+ return sha2563(Buffer3.concat([tagHash, tagHash, Buffer3.from(bytes)]));
2756
+ }
2748
2757
  function encodeMessageHash(message) {
2749
2758
  const messageBytes = Buffer3.from(message, "utf8");
2750
2759
  const magicBytes = Buffer3.from(MESSAGE_MAGIC, "utf8");
@@ -2784,6 +2793,19 @@ function decodePQAddress(address) {
2784
2793
  program: Buffer3.from(import_bech322.bech32m.fromWords(decoded.words.slice(1)))
2785
2794
  };
2786
2795
  }
2796
+ function getDefaultPQAuthScriptCommitment(serializedPublicKey) {
2797
+ const authDescriptor = Buffer3.concat([
2798
+ Buffer3.from([AUTHSCRIPT_DEFAULT_AUTH_TYPE]),
2799
+ hash1602(serializedPublicKey)
2800
+ ]);
2801
+ const witnessScriptHash = sha2563(AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT);
2802
+ const preimage = Buffer3.concat([
2803
+ Buffer3.from([AUTHSCRIPT_DOMAIN_SEPARATOR]),
2804
+ authDescriptor,
2805
+ witnessScriptHash
2806
+ ]);
2807
+ return taggedHash(AUTHSCRIPT_TAG, preimage);
2808
+ }
2787
2809
  function sign2(message, privateKey, compressed = true) {
2788
2810
  const signature = signLegacyMessage(
2789
2811
  message,
@@ -2843,10 +2865,12 @@ function verifyPQMessage(message, address, signature) {
2843
2865
  return false;
2844
2866
  }
2845
2867
  const decodedAddress = decodePQAddress(address);
2846
- if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {
2868
+ if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
2847
2869
  return false;
2848
2870
  }
2849
- const expectedProgram = hash1602(serializedPublicKey);
2871
+ const expectedProgram = getDefaultPQAuthScriptCommitment(
2872
+ serializedPublicKey
2873
+ );
2850
2874
  if (!expectedProgram.equals(decodedAddress.program)) {
2851
2875
  return false;
2852
2876
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neuraiproject/neurai-message",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Sign and Verify messages in Neurai",
5
5
  "source": "index.ts",
6
6
  "main": "dist/index.cjs",
package/src/core.ts CHANGED
@@ -10,6 +10,11 @@ const PQ_SERIALIZED_PUBKEY_PREFIX = 0x05;
10
10
  const PQ_PUBLIC_KEY_LENGTH = 1312;
11
11
  const PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;
12
12
  const PQ_SIGNATURE_LENGTH = 2420;
13
+ const AUTHSCRIPT_PROGRAM_LENGTH = 32;
14
+ const AUTHSCRIPT_DEFAULT_AUTH_TYPE = 0x01;
15
+ const AUTHSCRIPT_DOMAIN_SEPARATOR = 0x01;
16
+ const AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT = Buffer.from([0x51]); // OP_TRUE
17
+ const AUTHSCRIPT_TAG = "NeuraiAuthScript";
13
18
  const LEGACY_MESSAGE_PREFIX =
14
19
  String.fromCharCode(Buffer.byteLength(MESSAGE_MAGIC, "utf8")) +
15
20
  MESSAGE_MAGIC;
@@ -83,6 +88,11 @@ function hash160(bytes: Uint8Array) {
83
88
  return createHash("ripemd160").update(sha256(bytes)).digest();
84
89
  }
85
90
 
91
+ function taggedHash(tag: string, bytes: Uint8Array) {
92
+ const tagHash = sha256(Buffer.from(tag, "utf8"));
93
+ return sha256(Buffer.concat([tagHash, tagHash, Buffer.from(bytes)]));
94
+ }
95
+
86
96
  function encodeMessageHash(message: string) {
87
97
  const messageBytes = Buffer.from(message, "utf8");
88
98
  const magicBytes = Buffer.from(MESSAGE_MAGIC, "utf8");
@@ -137,6 +147,21 @@ function decodePQAddress(address: string) {
137
147
  };
138
148
  }
139
149
 
150
+ function getDefaultPQAuthScriptCommitment(serializedPublicKey: Uint8Array) {
151
+ const authDescriptor = Buffer.concat([
152
+ Buffer.from([AUTHSCRIPT_DEFAULT_AUTH_TYPE]),
153
+ hash160(serializedPublicKey),
154
+ ]);
155
+ const witnessScriptHash = sha256(AUTHSCRIPT_DEFAULT_WITNESS_SCRIPT);
156
+ const preimage = Buffer.concat([
157
+ Buffer.from([AUTHSCRIPT_DOMAIN_SEPARATOR]),
158
+ authDescriptor,
159
+ witnessScriptHash,
160
+ ]);
161
+
162
+ return taggedHash(AUTHSCRIPT_TAG, preimage);
163
+ }
164
+
140
165
  /** returns a base64 encoded string representation of the legacy signature */
141
166
  export function sign(message: string, privateKey: Uint8Array, compressed = true) {
142
167
  const signature = signLegacyMessage(
@@ -227,11 +252,16 @@ export function verifyPQMessage(
227
252
  }
228
253
 
229
254
  const decodedAddress = decodePQAddress(address);
230
- if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {
255
+ if (
256
+ decodedAddress.version !== 1 ||
257
+ decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH
258
+ ) {
231
259
  return false;
232
260
  }
233
261
 
234
- const expectedProgram = hash160(serializedPublicKey);
262
+ const expectedProgram = getDefaultPQAuthScriptCommitment(
263
+ serializedPublicKey
264
+ );
235
265
  if (!expectedProgram.equals(decodedAddress.program)) {
236
266
  return false;
237
267
  }
package/test.spec.js CHANGED
@@ -16,6 +16,30 @@ const address = "RVDUQTULaceEudDsgqCQBT6bfcdqUSvJPV";
16
16
  const message = "Hello world";
17
17
  const signature = sign(message, privateKey, compressed);
18
18
 
19
+ function sha256(bytes) {
20
+ return createHash("sha256").update(bytes).digest();
21
+ }
22
+
23
+ function taggedHash(tag, bytes) {
24
+ const tagHash = sha256(Buffer.from(tag, "utf8"));
25
+ return sha256(Buffer.concat([tagHash, tagHash, Buffer.from(bytes)]));
26
+ }
27
+
28
+ function createDefaultPQAuthScriptAddress(hrp, serializedPublicKey) {
29
+ const authDescriptor = Buffer.concat([
30
+ Buffer.from([0x01]),
31
+ createHash("ripemd160").update(sha256(serializedPublicKey)).digest(),
32
+ ]);
33
+ const witnessScriptHash = sha256(Buffer.from([0x51]));
34
+ const commitment = taggedHash(
35
+ "NeuraiAuthScript",
36
+ Buffer.concat([Buffer.from([0x01]), authDescriptor, witnessScriptHash])
37
+ );
38
+ const words = bech32m.toWords(commitment);
39
+ words.unshift(1);
40
+ return bech32m.encode(hrp, words);
41
+ }
42
+
19
43
  test("Verify valid message signature", () => {
20
44
  const result = verifyMessage(message, address, signature);
21
45
 
@@ -39,12 +63,7 @@ test("Verify valid PQ message signature", async () => {
39
63
  Buffer.from([0x05]),
40
64
  Buffer.from(keys.publicKey),
41
65
  ]);
42
- const program = createHash("ripemd160")
43
- .update(createHash("sha256").update(serializedPublicKey).digest())
44
- .digest();
45
- const words = bech32m.toWords(program);
46
- words.unshift(1);
47
- const pqAddress = bech32m.encode("tnq", words);
66
+ const pqAddress = createDefaultPQAuthScriptAddress("tnq", serializedPublicKey);
48
67
  const pqMessage = "Hello from PQ";
49
68
  const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
50
69
 
@@ -60,12 +79,7 @@ test("Reject invalid PQ message signature", async () => {
60
79
  Buffer.from([0x05]),
61
80
  Buffer.from(keys.publicKey),
62
81
  ]);
63
- const program = createHash("ripemd160")
64
- .update(createHash("sha256").update(serializedPublicKey).digest())
65
- .digest();
66
- const words = bech32m.toWords(program);
67
- words.unshift(1);
68
- const pqAddress = bech32m.encode("tnq", words);
82
+ const pqAddress = createDefaultPQAuthScriptAddress("tnq", serializedPublicKey);
69
83
  const pqMessage = "Hello from PQ";
70
84
  const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
71
85
 
@@ -73,3 +87,24 @@ test("Reject invalid PQ message signature", async () => {
73
87
  false
74
88
  );
75
89
  });
90
+
91
+ test("Reject old PQ witness-v1 keyhash addresses", async () => {
92
+ const { ml_dsa44 } = await import("@noble/post-quantum/ml-dsa.js");
93
+ const seed = Buffer.alloc(32, 11);
94
+ const keys = ml_dsa44.keygen(seed);
95
+ const serializedPublicKey = Buffer.concat([
96
+ Buffer.from([0x05]),
97
+ Buffer.from(keys.publicKey),
98
+ ]);
99
+ const oldProgram = createHash("ripemd160")
100
+ .update(sha256(serializedPublicKey))
101
+ .digest();
102
+ const words = bech32m.toWords(oldProgram);
103
+ words.unshift(1);
104
+ const oldPqAddress = bech32m.encode("tnq", words);
105
+ const pqMessage = "Hello from PQ";
106
+ const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
107
+
108
+ expect(verifyPQMessage(pqMessage, oldPqAddress, pqSignature)).toBe(false);
109
+ expect(verifyMessage(pqMessage, oldPqAddress, pqSignature)).toBe(false);
110
+ });