@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 +22 -6
- package/dist/NeuraiMessage.global.js +26 -2
- package/dist/browser.mjs +26 -2
- package/dist/index.cjs +26 -2
- package/dist/index.mjs +26 -2
- package/package.json +1 -1
- package/src/core.ts +32 -2
- package/test.spec.js +47 -12
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
|
|
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
|
-
-
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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 !==
|
|
12803
|
+
if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
|
|
12782
12804
|
return false;
|
|
12783
12805
|
}
|
|
12784
|
-
const expectedProgram =
|
|
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 !==
|
|
12777
|
+
if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
|
|
12756
12778
|
return false;
|
|
12757
12779
|
}
|
|
12758
|
-
const expectedProgram =
|
|
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 !==
|
|
2878
|
+
if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
|
|
2857
2879
|
return false;
|
|
2858
2880
|
}
|
|
2859
|
-
const expectedProgram =
|
|
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 !==
|
|
2868
|
+
if (decodedAddress.version !== 1 || decodedAddress.program.length !== AUTHSCRIPT_PROGRAM_LENGTH) {
|
|
2847
2869
|
return false;
|
|
2848
2870
|
}
|
|
2849
|
-
const expectedProgram =
|
|
2871
|
+
const expectedProgram = getDefaultPQAuthScriptCommitment(
|
|
2872
|
+
serializedPublicKey
|
|
2873
|
+
);
|
|
2850
2874
|
if (!expectedProgram.equals(decodedAddress.program)) {
|
|
2851
2875
|
return false;
|
|
2852
2876
|
}
|
package/package.json
CHANGED
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 (
|
|
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 =
|
|
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
|
|
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
|
|
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
|
+
});
|