@neuraiproject/neurai-message 0.8.1 → 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.
@@ -0,0 +1,140 @@
1
+ import { Buffer } from "buffer";
2
+ import { hmac } from "@noble/hashes/hmac.js";
3
+ import { sha256 as nobleSha256 } from "@noble/hashes/sha2.js";
4
+ import * as secp256k1 from "@noble/secp256k1";
5
+ import { bech32 } from "bech32";
6
+ import bs58check from "bs58check";
7
+ import createHash from "create-hash";
8
+ import varuint from "varuint-bitcoin";
9
+
10
+ secp256k1.hashes.hmacSha256 = (key, msg) => hmac(nobleSha256, key, msg);
11
+ secp256k1.hashes.sha256 = nobleSha256;
12
+
13
+ function sha256(bytes: Uint8Array) {
14
+ return createHash("sha256").update(bytes).digest();
15
+ }
16
+
17
+ function hash256(bytes: Uint8Array) {
18
+ return sha256(sha256(bytes));
19
+ }
20
+
21
+ function hash160(bytes: Uint8Array) {
22
+ return createHash("ripemd160").update(sha256(bytes)).digest();
23
+ }
24
+
25
+ function encodeCompactSignature(
26
+ signature: Uint8Array,
27
+ recovery: number,
28
+ compressed: boolean
29
+ ) {
30
+ let header = recovery + 27;
31
+ if (compressed) {
32
+ header += 4;
33
+ }
34
+ return Buffer.concat([Buffer.from([header]), Buffer.from(signature)]);
35
+ }
36
+
37
+ function decodeCompactSignature(buffer: Buffer) {
38
+ if (buffer.length !== 65) {
39
+ throw new Error("Invalid signature length");
40
+ }
41
+
42
+ const flagByte = buffer.readUInt8(0) - 27;
43
+ if (flagByte < 0 || flagByte > 15) {
44
+ throw new Error("Invalid signature parameter");
45
+ }
46
+
47
+ return {
48
+ compressed: !!(flagByte & 12),
49
+ recovery: flagByte & 3,
50
+ signature: buffer.subarray(1),
51
+ segwitType: !(flagByte & 8)
52
+ ? null
53
+ : !(flagByte & 4)
54
+ ? "p2sh(p2wpkh)"
55
+ : "p2wpkh",
56
+ };
57
+ }
58
+
59
+ function decodeBech32Address(address: string) {
60
+ const result = bech32.decode(address);
61
+ return Buffer.from(bech32.fromWords(result.words.slice(1)));
62
+ }
63
+
64
+ function segwitRedeemHash(publicKeyHash: Uint8Array) {
65
+ const redeemScript = Buffer.concat([
66
+ Buffer.from("0014", "hex"),
67
+ Buffer.from(publicKeyHash),
68
+ ]);
69
+ return hash160(redeemScript);
70
+ }
71
+
72
+ export function magicHash(message: string | Buffer, messagePrefix: string | Buffer) {
73
+ const prefix = Buffer.isBuffer(messagePrefix)
74
+ ? messagePrefix
75
+ : Buffer.from(messagePrefix, "utf8");
76
+ const payload = Buffer.isBuffer(message)
77
+ ? message
78
+ : Buffer.from(message, "utf8");
79
+ const messageVISize = varuint.encodingLength(payload.length);
80
+ const buffer = Buffer.allocUnsafe(prefix.length + messageVISize + payload.length);
81
+
82
+ prefix.copy(buffer, 0);
83
+ varuint.encode(payload.length, buffer, prefix.length);
84
+ payload.copy(buffer, prefix.length + messageVISize);
85
+
86
+ return hash256(buffer);
87
+ }
88
+
89
+ export function signLegacyMessage(
90
+ message: string,
91
+ privateKey: Uint8Array,
92
+ compressed: boolean,
93
+ messagePrefix: string | Buffer
94
+ ) {
95
+ const hash = magicHash(message, messagePrefix);
96
+ const recoveredSignature = secp256k1.sign(hash, Buffer.from(privateKey), {
97
+ prehash: false,
98
+ format: "recovered",
99
+ });
100
+ return encodeCompactSignature(
101
+ recoveredSignature.subarray(1),
102
+ recoveredSignature[0],
103
+ compressed
104
+ );
105
+ }
106
+
107
+ export function verifyLegacyCompactMessage(
108
+ message: string,
109
+ address: string,
110
+ signature: Uint8Array,
111
+ messagePrefix: string | Buffer
112
+ ) {
113
+ const parsed = decodeCompactSignature(Buffer.from(signature));
114
+ const hash = magicHash(message, messagePrefix);
115
+ const recoveredSignature = Buffer.concat([
116
+ Buffer.from([parsed.recovery]),
117
+ Buffer.from(parsed.signature),
118
+ ]);
119
+ const publicKey = Buffer.from(
120
+ secp256k1.recoverPublicKey(recoveredSignature, hash, {
121
+ prehash: false,
122
+ })
123
+ );
124
+ const normalizedPublicKey = parsed.compressed
125
+ ? publicKey
126
+ : Buffer.from(secp256k1.Point.fromBytes(publicKey).toBytes(false));
127
+ const publicKeyHash = hash160(normalizedPublicKey);
128
+
129
+ if (parsed.segwitType === "p2sh(p2wpkh)") {
130
+ return segwitRedeemHash(publicKeyHash).equals(
131
+ Buffer.from(bs58check.decode(address).slice(1))
132
+ );
133
+ }
134
+
135
+ if (parsed.segwitType === "p2wpkh") {
136
+ return publicKeyHash.equals(decodeBech32Address(address));
137
+ }
138
+
139
+ return publicKeyHash.equals(Buffer.from(bs58check.decode(address).slice(1)));
140
+ }
package/test.spec.js ADDED
@@ -0,0 +1,110 @@
1
+ const { createHash } = require("crypto");
2
+ const { bech32m } = require("bech32");
3
+ const {
4
+ sign,
5
+ signPQMessage,
6
+ verifyMessage,
7
+ verifyPQMessage,
8
+ } = require("./dist/index.cjs");
9
+
10
+ const compressed = true;
11
+ const privateKey = Buffer.from(
12
+ "79b4c20524324622cacbf7a7b428542e90d674274b99e3f54816d447e57412ae",
13
+ "hex"
14
+ );
15
+ const address = "RVDUQTULaceEudDsgqCQBT6bfcdqUSvJPV";
16
+ const message = "Hello world";
17
+ const signature = sign(message, privateKey, compressed);
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
+
43
+ test("Verify valid message signature", () => {
44
+ const result = verifyMessage(message, address, signature);
45
+
46
+ expect(result).toBe(true);
47
+ });
48
+
49
+ test("Verify unvalid message signature", () => {
50
+ const result = verifyMessage(
51
+ message + " change the message",
52
+ address,
53
+ signature
54
+ );
55
+ expect(result).toBe(false);
56
+ });
57
+
58
+ test("Verify valid PQ message signature", async () => {
59
+ const { ml_dsa44 } = await import("@noble/post-quantum/ml-dsa.js");
60
+ const seed = Buffer.alloc(32, 7);
61
+ const keys = ml_dsa44.keygen(seed);
62
+ const serializedPublicKey = Buffer.concat([
63
+ Buffer.from([0x05]),
64
+ Buffer.from(keys.publicKey),
65
+ ]);
66
+ const pqAddress = createDefaultPQAuthScriptAddress("tnq", serializedPublicKey);
67
+ const pqMessage = "Hello from PQ";
68
+ const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
69
+
70
+ expect(verifyPQMessage(pqMessage, pqAddress, pqSignature)).toBe(true);
71
+ expect(verifyMessage(pqMessage, pqAddress, pqSignature)).toBe(true);
72
+ });
73
+
74
+ test("Reject invalid PQ message signature", async () => {
75
+ const { ml_dsa44 } = await import("@noble/post-quantum/ml-dsa.js");
76
+ const seed = Buffer.alloc(32, 9);
77
+ const keys = ml_dsa44.keygen(seed);
78
+ const serializedPublicKey = Buffer.concat([
79
+ Buffer.from([0x05]),
80
+ Buffer.from(keys.publicKey),
81
+ ]);
82
+ const pqAddress = createDefaultPQAuthScriptAddress("tnq", serializedPublicKey);
83
+ const pqMessage = "Hello from PQ";
84
+ const pqSignature = signPQMessage(pqMessage, keys.secretKey, keys.publicKey);
85
+
86
+ expect(verifyMessage(pqMessage + " changed", pqAddress, pqSignature)).toBe(
87
+ false
88
+ );
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
+ });
package/tsconfig.json CHANGED
@@ -12,5 +12,5 @@
12
12
  "emitDeclarationOnly": true,
13
13
  "outDir": "dist"
14
14
  },
15
- "include": ["index.ts"]
16
- }
15
+ "include": ["index.ts", "src/**/*.ts"]
16
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ },
7
+ });
package/test.js DELETED
@@ -1,77 +0,0 @@
1
- const { createHash } = require("crypto");
2
- const { bech32m } = require("bech32");
3
- const {
4
- sign,
5
- signPQMessage,
6
- verifyMessage,
7
- verifyPQMessage,
8
- } = require("./dist/main");
9
-
10
- const compressed = true;
11
- const privateKey = Buffer.from(
12
- "79b4c20524324622cacbf7a7b428542e90d674274b99e3f54816d447e57412ae",
13
- "hex"
14
- );
15
- const address = "RVDUQTULaceEudDsgqCQBT6bfcdqUSvJPV";
16
- const message = "Hello world";
17
- const signature = sign(message, privateKey, compressed);
18
-
19
- test("Verify valid message signature", () => {
20
- const result = verifyMessage(message, address, signature);
21
-
22
- expect(result).toBe(true);
23
- });
24
-
25
- test("Verify unvalid message signature", () => {
26
- const result = verifyMessage(
27
- message + " change the message",
28
- address,
29
- signature
30
- );
31
- expect(result).toBe(false);
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
- });