@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.
- package/LICENSE +1 -1
- package/README.md +47 -0
- package/dist/main.js +17646 -0
- package/dist/main.js.map +1 -0
- package/dist/module.js +17641 -0
- package/dist/module.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -0
- package/index.ts +243 -10
- package/package.json +15 -5
- package/test.js +60 -4
- package/tsconfig.json +13 -0
package/dist/types.d.ts
ADDED
|
@@ -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
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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.
|
|
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": "
|
|
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": "
|
|
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 {
|
|
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
|
|
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