@neuraiproject/neurai-message 0.7.0 → 0.8.0

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.
@@ -0,0 +1,8 @@
1
+ /** returns a base64 encoded string representation of the legacy signature */
2
+ export function sign(message: string, privateKey: Uint8Array, compressed?: boolean): any;
3
+ export function signPQMessage(message: string, privateKey: Uint8Array, publicKey: Uint8Array): string;
4
+ export function verifyLegacyMessage(message: string, address: string, signature: string | Uint8Array): any;
5
+ export function verifyPQMessage(message: string, address: string, signature: string | Uint8Array): any;
6
+ export function verifyMessage(message: string, address: string, signature: string | Uint8Array): any;
7
+
8
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"mappings":"AA0IA,6EAA6E;AAC7E,qBAAqB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,UAAO,OAS9E;AAED,8BACE,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,UAAU,UAetB;AAED,oCACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,UAAU,OAY/B;AAED,gCACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,UAAU,OAuD/B;AAED,8BACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAAG,UAAU,OAK/B","sources":["index.ts"],"sourcesContent":["import { createHash } from \"crypto\";\nimport { bech32m } from \"bech32\";\nimport * as bitcoinMessage from \"bitcoinjs-message\";\nimport { ml_dsa44 } from \"@noble/post-quantum/ml-dsa.js\";\n\nconst MESSAGE_MAGIC = \"Neurai Signed Message:\\n\";\nconst PQ_MESSAGE_SIGNATURE_PREFIX = 0x35;\nconst PQ_SERIALIZED_PUBKEY_PREFIX = 0x05;\nconst PQ_PUBLIC_KEY_LENGTH = 1312;\nconst PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;\nconst PQ_SIGNATURE_LENGTH = 2420;\nconst LEGACY_MESSAGE_PREFIX =\n String.fromCharCode(Buffer.byteLength(MESSAGE_MAGIC, \"utf8\")) +\n MESSAGE_MAGIC;\n\nfunction encodeCompactSize(value: number): Buffer {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(\"CompactSize value must be a non-negative integer\");\n }\n\n if (value < 253) {\n return Buffer.from([value]);\n }\n\n if (value <= 0xffff) {\n const buffer = Buffer.alloc(3);\n buffer[0] = 0xfd;\n buffer.writeUInt16LE(value, 1);\n return buffer;\n }\n\n if (value <= 0xffffffff) {\n const buffer = Buffer.alloc(5);\n buffer[0] = 0xfe;\n buffer.writeUInt32LE(value, 1);\n return buffer;\n }\n\n throw new Error(\"CompactSize values above uint32 are not supported\");\n}\n\nfunction decodeCompactSize(buffer: Buffer, offset: number) {\n if (offset >= buffer.length) {\n throw new Error(\"Unexpected end of CompactSize data\");\n }\n\n const first = buffer[offset];\n if (first < 253) {\n return { value: first, offset: offset + 1 };\n }\n\n if (first === 0xfd) {\n if (offset + 3 > buffer.length) {\n throw new Error(\"Unexpected end of CompactSize uint16 data\");\n }\n return { value: buffer.readUInt16LE(offset + 1), offset: offset + 3 };\n }\n\n if (first === 0xfe) {\n if (offset + 5 > buffer.length) {\n throw new Error(\"Unexpected end of CompactSize uint32 data\");\n }\n return { value: buffer.readUInt32LE(offset + 1), offset: offset + 5 };\n }\n\n if (first === 0xff) {\n throw new Error(\"CompactSize uint64 is not supported\");\n }\n\n throw new Error(\"Invalid CompactSize prefix\");\n}\n\nfunction sha256(bytes: Uint8Array) {\n return createHash(\"sha256\").update(bytes).digest();\n}\n\nfunction hash256(bytes: Uint8Array) {\n return sha256(sha256(bytes));\n}\n\nfunction hash160(bytes: Uint8Array) {\n return createHash(\"ripemd160\").update(sha256(bytes)).digest();\n}\n\nfunction encodeMessageHash(message: string) {\n const messageBytes = Buffer.from(message, \"utf8\");\n const magicBytes = Buffer.from(MESSAGE_MAGIC, \"utf8\");\n const payload = Buffer.concat([\n encodeCompactSize(magicBytes.length),\n magicBytes,\n encodeCompactSize(messageBytes.length),\n messageBytes,\n ]);\n\n return hash256(payload);\n}\n\nfunction toSignatureBuffer(signature: string | Uint8Array) {\n return typeof signature === \"string\"\n ? Buffer.from(signature, \"base64\")\n : Buffer.from(signature);\n}\n\nfunction normalizePQPublicKey(publicKey: Uint8Array) {\n const buffer = Buffer.from(publicKey);\n\n if (\n buffer.length === PQ_SERIALIZED_PUBKEY_LENGTH &&\n buffer[0] === PQ_SERIALIZED_PUBKEY_PREFIX\n ) {\n return buffer;\n }\n\n if (buffer.length === PQ_PUBLIC_KEY_LENGTH) {\n return Buffer.concat([Buffer.from([PQ_SERIALIZED_PUBKEY_PREFIX]), buffer]);\n }\n\n throw new Error(\"Invalid PQ public key length\");\n}\n\nfunction isPQMessageSignature(signature: string | Uint8Array) {\n const buffer = toSignatureBuffer(signature);\n return buffer.length > 0 && buffer[0] === PQ_MESSAGE_SIGNATURE_PREFIX;\n}\n\nfunction decodePQAddress(address: string) {\n const decoded = bech32m.decode(address);\n if (decoded.words.length === 0) {\n throw new Error(\"Invalid bech32m address\");\n }\n\n return {\n prefix: decoded.prefix,\n version: decoded.words[0],\n program: Buffer.from(bech32m.fromWords(decoded.words.slice(1))),\n };\n}\n\n/** returns a base64 encoded string representation of the legacy signature */\nexport function sign(message: string, privateKey: Uint8Array, compressed = true) {\n const signature = bitcoinMessage.sign(\n message,\n Buffer.from(privateKey),\n compressed,\n LEGACY_MESSAGE_PREFIX\n );\n\n return signature.toString(\"base64\");\n}\n\nexport function signPQMessage(\n message: string,\n privateKey: Uint8Array,\n publicKey: Uint8Array\n) {\n const serializedPublicKey = normalizePQPublicKey(publicKey);\n const hash = encodeMessageHash(message);\n const pqSignature = Buffer.from(ml_dsa44.sign(hash, Buffer.from(privateKey)));\n\n const payload = Buffer.concat([\n Buffer.from([PQ_MESSAGE_SIGNATURE_PREFIX]),\n encodeCompactSize(serializedPublicKey.length),\n serializedPublicKey,\n encodeCompactSize(pqSignature.length),\n pqSignature,\n ]);\n\n return payload.toString(\"base64\");\n}\n\nexport function verifyLegacyMessage(\n message: string,\n address: string,\n signature: string | Uint8Array\n) {\n try {\n return bitcoinMessage.verify(\n message,\n address,\n toSignatureBuffer(signature),\n LEGACY_MESSAGE_PREFIX\n );\n } catch {\n return false;\n }\n}\n\nexport function verifyPQMessage(\n message: string,\n address: string,\n signature: string | Uint8Array\n) {\n try {\n const payload = toSignatureBuffer(signature);\n let offset = 0;\n\n if (payload[offset++] !== PQ_MESSAGE_SIGNATURE_PREFIX) {\n return false;\n }\n\n const publicKeyLength = decodeCompactSize(payload, offset);\n offset = publicKeyLength.offset;\n\n const serializedPublicKey = payload.subarray(\n offset,\n offset + publicKeyLength.value\n );\n offset += publicKeyLength.value;\n\n const signatureLength = decodeCompactSize(payload, offset);\n offset = signatureLength.offset;\n\n const pqSignature = payload.subarray(offset, offset + signatureLength.value);\n offset += signatureLength.value;\n\n if (offset !== payload.length) {\n return false;\n }\n\n if (\n serializedPublicKey.length !== PQ_SERIALIZED_PUBKEY_LENGTH ||\n serializedPublicKey[0] !== PQ_SERIALIZED_PUBKEY_PREFIX ||\n pqSignature.length !== PQ_SIGNATURE_LENGTH\n ) {\n return false;\n }\n\n const decodedAddress = decodePQAddress(address);\n if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {\n return false;\n }\n\n const expectedProgram = hash160(serializedPublicKey);\n if (!expectedProgram.equals(decodedAddress.program)) {\n return false;\n }\n\n return ml_dsa44.verify(\n pqSignature,\n encodeMessageHash(message),\n serializedPublicKey.subarray(1)\n );\n } catch {\n return false;\n }\n}\n\nexport function verifyMessage(\n message: string,\n address: string,\n signature: string | Uint8Array\n) {\n return isPQMessageSignature(signature)\n ? verifyPQMessage(message, address, signature)\n : verifyLegacyMessage(message, address, signature);\n}\n"],"names":[],"version":3,"file":"types.d.ts.map"}
package/index.ts CHANGED
@@ -1,23 +1,256 @@
1
- import bitcoinMessage from "bitcoinjs-message";
2
- const MESSAGE_PREFIX = "\x16Neurai Signed Message:\n";
1
+ import { createHash } from "crypto";
2
+ import { bech32m } from "bech32";
3
+ import * as bitcoinMessage from "bitcoinjs-message";
4
+ import { ml_dsa44 } from "@noble/post-quantum/ml-dsa.js";
3
5
 
4
- /** returns a base64 encoded string representation of the signature */
5
- export function sign(message: string, privateKey: any, compressed = true) {
6
+ const MESSAGE_MAGIC = "Neurai Signed Message:\n";
7
+ const PQ_MESSAGE_SIGNATURE_PREFIX = 0x35;
8
+ const PQ_SERIALIZED_PUBKEY_PREFIX = 0x05;
9
+ const PQ_PUBLIC_KEY_LENGTH = 1312;
10
+ const PQ_SERIALIZED_PUBKEY_LENGTH = 1 + PQ_PUBLIC_KEY_LENGTH;
11
+ const PQ_SIGNATURE_LENGTH = 2420;
12
+ const LEGACY_MESSAGE_PREFIX =
13
+ String.fromCharCode(Buffer.byteLength(MESSAGE_MAGIC, "utf8")) +
14
+ MESSAGE_MAGIC;
15
+
16
+ function encodeCompactSize(value: number): Buffer {
17
+ if (!Number.isInteger(value) || value < 0) {
18
+ throw new Error("CompactSize value must be a non-negative integer");
19
+ }
20
+
21
+ if (value < 253) {
22
+ return Buffer.from([value]);
23
+ }
24
+
25
+ if (value <= 0xffff) {
26
+ const buffer = Buffer.alloc(3);
27
+ buffer[0] = 0xfd;
28
+ buffer.writeUInt16LE(value, 1);
29
+ return buffer;
30
+ }
31
+
32
+ if (value <= 0xffffffff) {
33
+ const buffer = Buffer.alloc(5);
34
+ buffer[0] = 0xfe;
35
+ buffer.writeUInt32LE(value, 1);
36
+ return buffer;
37
+ }
38
+
39
+ throw new Error("CompactSize values above uint32 are not supported");
40
+ }
41
+
42
+ function decodeCompactSize(buffer: Buffer, offset: number) {
43
+ if (offset >= buffer.length) {
44
+ throw new Error("Unexpected end of CompactSize data");
45
+ }
46
+
47
+ const first = buffer[offset];
48
+ if (first < 253) {
49
+ return { value: first, offset: offset + 1 };
50
+ }
51
+
52
+ if (first === 0xfd) {
53
+ if (offset + 3 > buffer.length) {
54
+ throw new Error("Unexpected end of CompactSize uint16 data");
55
+ }
56
+ return { value: buffer.readUInt16LE(offset + 1), offset: offset + 3 };
57
+ }
58
+
59
+ if (first === 0xfe) {
60
+ if (offset + 5 > buffer.length) {
61
+ throw new Error("Unexpected end of CompactSize uint32 data");
62
+ }
63
+ return { value: buffer.readUInt32LE(offset + 1), offset: offset + 5 };
64
+ }
65
+
66
+ if (first === 0xff) {
67
+ throw new Error("CompactSize uint64 is not supported");
68
+ }
69
+
70
+ throw new Error("Invalid CompactSize prefix");
71
+ }
72
+
73
+ function sha256(bytes: Uint8Array) {
74
+ return createHash("sha256").update(bytes).digest();
75
+ }
76
+
77
+ function hash256(bytes: Uint8Array) {
78
+ return sha256(sha256(bytes));
79
+ }
80
+
81
+ function hash160(bytes: Uint8Array) {
82
+ return createHash("ripemd160").update(sha256(bytes)).digest();
83
+ }
84
+
85
+ function encodeMessageHash(message: string) {
86
+ const messageBytes = Buffer.from(message, "utf8");
87
+ const magicBytes = Buffer.from(MESSAGE_MAGIC, "utf8");
88
+ const payload = Buffer.concat([
89
+ encodeCompactSize(magicBytes.length),
90
+ magicBytes,
91
+ encodeCompactSize(messageBytes.length),
92
+ messageBytes,
93
+ ]);
94
+
95
+ return hash256(payload);
96
+ }
97
+
98
+ function toSignatureBuffer(signature: string | Uint8Array) {
99
+ return typeof signature === "string"
100
+ ? Buffer.from(signature, "base64")
101
+ : Buffer.from(signature);
102
+ }
103
+
104
+ function normalizePQPublicKey(publicKey: Uint8Array) {
105
+ const buffer = Buffer.from(publicKey);
106
+
107
+ if (
108
+ buffer.length === PQ_SERIALIZED_PUBKEY_LENGTH &&
109
+ buffer[0] === PQ_SERIALIZED_PUBKEY_PREFIX
110
+ ) {
111
+ return buffer;
112
+ }
113
+
114
+ if (buffer.length === PQ_PUBLIC_KEY_LENGTH) {
115
+ return Buffer.concat([Buffer.from([PQ_SERIALIZED_PUBKEY_PREFIX]), buffer]);
116
+ }
117
+
118
+ throw new Error("Invalid PQ public key length");
119
+ }
120
+
121
+ function isPQMessageSignature(signature: string | Uint8Array) {
122
+ const buffer = toSignatureBuffer(signature);
123
+ return buffer.length > 0 && buffer[0] === PQ_MESSAGE_SIGNATURE_PREFIX;
124
+ }
125
+
126
+ function decodePQAddress(address: string) {
127
+ const decoded = bech32m.decode(address);
128
+ if (decoded.words.length === 0) {
129
+ throw new Error("Invalid bech32m address");
130
+ }
131
+
132
+ return {
133
+ prefix: decoded.prefix,
134
+ version: decoded.words[0],
135
+ program: Buffer.from(bech32m.fromWords(decoded.words.slice(1))),
136
+ };
137
+ }
138
+
139
+ /** returns a base64 encoded string representation of the legacy signature */
140
+ export function sign(message: string, privateKey: Uint8Array, compressed = true) {
6
141
  const signature = bitcoinMessage.sign(
7
142
  message,
8
- privateKey,
143
+ Buffer.from(privateKey),
9
144
  compressed,
10
- MESSAGE_PREFIX
145
+ LEGACY_MESSAGE_PREFIX
11
146
  );
12
147
 
13
148
  return signature.toString("base64");
14
149
  }
150
+
151
+ export function signPQMessage(
152
+ message: string,
153
+ privateKey: Uint8Array,
154
+ publicKey: Uint8Array
155
+ ) {
156
+ const serializedPublicKey = normalizePQPublicKey(publicKey);
157
+ const hash = encodeMessageHash(message);
158
+ const pqSignature = Buffer.from(ml_dsa44.sign(hash, Buffer.from(privateKey)));
159
+
160
+ const payload = Buffer.concat([
161
+ Buffer.from([PQ_MESSAGE_SIGNATURE_PREFIX]),
162
+ encodeCompactSize(serializedPublicKey.length),
163
+ serializedPublicKey,
164
+ encodeCompactSize(pqSignature.length),
165
+ pqSignature,
166
+ ]);
167
+
168
+ return payload.toString("base64");
169
+ }
170
+
171
+ export function verifyLegacyMessage(
172
+ message: string,
173
+ address: string,
174
+ signature: string | Uint8Array
175
+ ) {
176
+ try {
177
+ return bitcoinMessage.verify(
178
+ message,
179
+ address,
180
+ toSignatureBuffer(signature),
181
+ LEGACY_MESSAGE_PREFIX
182
+ );
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+
188
+ export function verifyPQMessage(
189
+ message: string,
190
+ address: string,
191
+ signature: string | Uint8Array
192
+ ) {
193
+ try {
194
+ const payload = toSignatureBuffer(signature);
195
+ let offset = 0;
196
+
197
+ if (payload[offset++] !== PQ_MESSAGE_SIGNATURE_PREFIX) {
198
+ return false;
199
+ }
200
+
201
+ const publicKeyLength = decodeCompactSize(payload, offset);
202
+ offset = publicKeyLength.offset;
203
+
204
+ const serializedPublicKey = payload.subarray(
205
+ offset,
206
+ offset + publicKeyLength.value
207
+ );
208
+ offset += publicKeyLength.value;
209
+
210
+ const signatureLength = decodeCompactSize(payload, offset);
211
+ offset = signatureLength.offset;
212
+
213
+ const pqSignature = payload.subarray(offset, offset + signatureLength.value);
214
+ offset += signatureLength.value;
215
+
216
+ if (offset !== payload.length) {
217
+ return false;
218
+ }
219
+
220
+ if (
221
+ serializedPublicKey.length !== PQ_SERIALIZED_PUBKEY_LENGTH ||
222
+ serializedPublicKey[0] !== PQ_SERIALIZED_PUBKEY_PREFIX ||
223
+ pqSignature.length !== PQ_SIGNATURE_LENGTH
224
+ ) {
225
+ return false;
226
+ }
227
+
228
+ const decodedAddress = decodePQAddress(address);
229
+ if (decodedAddress.version !== 1 || decodedAddress.program.length !== 20) {
230
+ return false;
231
+ }
232
+
233
+ const expectedProgram = hash160(serializedPublicKey);
234
+ if (!expectedProgram.equals(decodedAddress.program)) {
235
+ return false;
236
+ }
237
+
238
+ return ml_dsa44.verify(
239
+ pqSignature,
240
+ encodeMessageHash(message),
241
+ serializedPublicKey.subarray(1)
242
+ );
243
+ } catch {
244
+ return false;
245
+ }
246
+ }
247
+
15
248
  export function verifyMessage(
16
249
  message: string,
17
250
  address: string,
18
- signature: string
251
+ signature: string | Uint8Array
19
252
  ) {
20
- const m = Buffer.from(message).toString("ascii");
21
- const result = bitcoinMessage.verify(m, address, signature, MESSAGE_PREFIX);
22
- return result;
253
+ return isPQMessageSignature(signature)
254
+ ? verifyPQMessage(message, address, signature)
255
+ : verifyLegacyMessage(message, address, signature);
23
256
  }
package/package.json CHANGED
@@ -1,14 +1,22 @@
1
1
  {
2
2
  "name": "@neuraiproject/neurai-message",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Sign and Verify messages in Neurai",
5
5
  "source": "index.ts",
6
6
  "main": "dist/main.js",
7
7
  "module": "dist/module.js",
8
8
  "types": "dist/types.d.ts",
9
+ "targets": {
10
+ "main": {
11
+ "includeNodeModules": true
12
+ },
13
+ "module": {
14
+ "includeNodeModules": true
15
+ }
16
+ },
9
17
  "scripts": {
10
- "test": "jest",
11
- "build": "npx parcel build"
18
+ "test": "npm run build && node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
19
+ "build": "npx parcel build index.ts --no-autoinstall"
12
20
  },
13
21
  "repository": {
14
22
  "type": "git",
@@ -18,18 +26,20 @@
18
26
  "Neurai",
19
27
  "XNA"
20
28
  ],
21
- "author": "Dick Henrik Larsson / Neurai",
29
+ "author": "Neuraiproject",
22
30
  "license": "ISC",
23
31
  "bugs": {
24
32
  "url": "https://github.com/neuraiproject/neurai-message/issues"
25
33
  },
26
34
  "homepage": "https://github.com/neuraiproject/neurai-message#readme",
27
35
  "dependencies": {
36
+ "@noble/post-quantum": "^0.6.0",
37
+ "bech32": "^2.0.0",
28
38
  "bitcoinjs-message": "^2.2.0"
29
39
  },
30
40
  "devDependencies": {
31
41
  "@parcel/packager-ts": "^2.8.3",
32
- "@parcel/transformer-typescript-types": "^2.8.3",
42
+ "@parcel/transformer-typescript-types": "2.16.4",
33
43
  "@types/jest": "^28.1.4",
34
44
  "@types/node": "^18.0.0",
35
45
  "jest": "^29.4.0",
package/test.js CHANGED
@@ -1,9 +1,20 @@
1
- const { verifyMessage } = require("./dist/main");
1
+ const { createHash } = require("crypto");
2
+ const { bech32m } = require("bech32");
3
+ const {
4
+ sign,
5
+ signPQMessage,
6
+ verifyMessage,
7
+ verifyPQMessage,
8
+ } = require("./dist/main");
2
9
 
3
- const address = "RS4EYELZhxMtDAuyrQimVrcSnaeaLCXeo6";
10
+ const compressed = true;
11
+ const privateKey = Buffer.from(
12
+ "79b4c20524324622cacbf7a7b428542e90d674274b99e3f54816d447e57412ae",
13
+ "hex"
14
+ );
15
+ const address = "RVDUQTULaceEudDsgqCQBT6bfcdqUSvJPV";
4
16
  const message = "Hello world";
5
- const signature =
6
- "H2zo48+tI/KT9eJrHt7PLiEBMaRn1A1Eh49IFu0MbfhAFBxVc0FG2UE5E79PCbhd9KexijsQxYvNM6AsVn9EAEo=";
17
+ const signature = sign(message, privateKey, compressed);
7
18
 
8
19
  test("Verify valid message signature", () => {
9
20
  const result = verifyMessage(message, address, signature);
@@ -19,3 +30,48 @@ test("Verify unvalid message signature", () => {
19
30
  );
20
31
  expect(result).toBe(false);
21
32
  });
33
+
34
+ test("Verify valid PQ message signature", () => {
35
+ return import("@noble/post-quantum/ml-dsa.js").then(({ ml_dsa44 }) => {
36
+ const seed = Buffer.alloc(32, 7);
37
+ const keys = ml_dsa44.keygen(seed);
38
+ const serializedPublicKey = Buffer.concat([
39
+ Buffer.from([0x05]),
40
+ Buffer.from(keys.publicKey),
41
+ ]);
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);
48
+ const pqMessage = "Hello from PQ";
49
+ const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
50
+
51
+ expect(verifyPQMessage(pqMessage, pqAddress, pqSignature)).toBe(true);
52
+ expect(verifyMessage(pqMessage, pqAddress, pqSignature)).toBe(true);
53
+ });
54
+ });
55
+
56
+ test("Reject invalid PQ message signature", () => {
57
+ return import("@noble/post-quantum/ml-dsa.js").then(({ ml_dsa44 }) => {
58
+ const seed = Buffer.alloc(32, 9);
59
+ const keys = ml_dsa44.keygen(seed);
60
+ const serializedPublicKey = Buffer.concat([
61
+ Buffer.from([0x05]),
62
+ Buffer.from(keys.publicKey),
63
+ ]);
64
+ const program = createHash("ripemd160")
65
+ .update(createHash("sha256").update(serializedPublicKey).digest())
66
+ .digest();
67
+ const words = bech32m.toWords(program);
68
+ words.unshift(1);
69
+ const pqAddress = bech32m.encode("tnq", words);
70
+ const pqMessage = "Hello from PQ";
71
+ const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
72
+
73
+ expect(verifyMessage(pqMessage + " changed", pqAddress, pqSignature)).toBe(
74
+ false
75
+ );
76
+ });
77
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "lib": ["ES2020"],
7
+ "types": ["node"],
8
+ "strict": false,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["index.ts"]
13
+ }