@neuraiproject/neurai-message 0.8.1 → 0.9.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/README.md +40 -8
- package/dist/NeuraiMessage.global.js +12837 -0
- package/dist/browser.mjs +12810 -0
- package/dist/index.cjs +2901 -0
- package/dist/index.d.ts +1 -6
- package/dist/index.mjs +2890 -0
- package/dist/src/browser-shims.d.ts +1 -0
- package/dist/src/browser.d.ts +1 -0
- package/dist/src/core.d.ts +6 -0
- package/dist/src/global.d.ts +8 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/legacy-message.d.ts +6 -0
- package/index.ts +1 -256
- package/package.json +33 -9
- package/src/browser-shims.ts +9 -0
- package/src/browser.ts +1 -0
- package/src/core.ts +257 -0
- package/src/global.ts +15 -0
- package/src/index.ts +1 -0
- package/src/legacy-message.ts +140 -0
- package/{test.js → test.spec.js} +5 -7
- package/tsconfig.json +2 -2
- package/vitest.config.mjs +7 -0
|
@@ -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.js → test.spec.js}
RENAMED
|
@@ -5,7 +5,7 @@ const {
|
|
|
5
5
|
signPQMessage,
|
|
6
6
|
verifyMessage,
|
|
7
7
|
verifyPQMessage,
|
|
8
|
-
} = require("./dist/
|
|
8
|
+
} = require("./dist/index.cjs");
|
|
9
9
|
|
|
10
10
|
const compressed = true;
|
|
11
11
|
const privateKey = Buffer.from(
|
|
@@ -31,8 +31,8 @@ test("Verify unvalid message signature", () => {
|
|
|
31
31
|
expect(result).toBe(false);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
test("Verify valid PQ message signature", () => {
|
|
35
|
-
|
|
34
|
+
test("Verify valid PQ message signature", async () => {
|
|
35
|
+
const { ml_dsa44 } = await import("@noble/post-quantum/ml-dsa.js");
|
|
36
36
|
const seed = Buffer.alloc(32, 7);
|
|
37
37
|
const keys = ml_dsa44.keygen(seed);
|
|
38
38
|
const serializedPublicKey = Buffer.concat([
|
|
@@ -50,11 +50,10 @@ test("Verify valid PQ message signature", () => {
|
|
|
50
50
|
|
|
51
51
|
expect(verifyPQMessage(pqMessage, pqAddress, pqSignature)).toBe(true);
|
|
52
52
|
expect(verifyMessage(pqMessage, pqAddress, pqSignature)).toBe(true);
|
|
53
|
-
});
|
|
54
53
|
});
|
|
55
54
|
|
|
56
|
-
test("Reject invalid PQ message signature", () => {
|
|
57
|
-
|
|
55
|
+
test("Reject invalid PQ message signature", async () => {
|
|
56
|
+
const { ml_dsa44 } = await import("@noble/post-quantum/ml-dsa.js");
|
|
58
57
|
const seed = Buffer.alloc(32, 9);
|
|
59
58
|
const keys = ml_dsa44.keygen(seed);
|
|
60
59
|
const serializedPublicKey = Buffer.concat([
|
|
@@ -73,5 +72,4 @@ test("Reject invalid PQ message signature", () => {
|
|
|
73
72
|
expect(verifyMessage(pqMessage + " changed", pqAddress, pqSignature)).toBe(
|
|
74
73
|
false
|
|
75
74
|
);
|
|
76
|
-
});
|
|
77
75
|
});
|
package/tsconfig.json
CHANGED