@majikah/majik-slink 0.0.2 → 0.2.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 +66 -66
- package/README.md +116 -37
- package/dist/core/types.d.ts +65 -25
- package/dist/core/utils.d.ts +9 -1
- package/dist/core/utils.js +17 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/majik-slink.d.ts +103 -34
- package/dist/majik-slink.js +153 -57
- package/package.json +11 -6
package/LICENSE
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
Copyright (c)
|
|
2
|
-
|
|
3
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
you may not use this file except in compliance with the License.
|
|
5
|
-
You may obtain a copy of the License at
|
|
6
|
-
|
|
7
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
|
|
9
|
-
Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
See the License for the specific language governing permissions and
|
|
13
|
-
limitations under the License.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
Apache License
|
|
18
|
-
Version 2.0, January 2004
|
|
19
|
-
http://www.apache.org/licenses/
|
|
20
|
-
|
|
21
|
-
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
22
|
-
|
|
23
|
-
1. Definitions.
|
|
24
|
-
|
|
25
|
-
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
26
|
-
|
|
27
|
-
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
28
|
-
|
|
29
|
-
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
30
|
-
|
|
31
|
-
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
32
|
-
|
|
33
|
-
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
34
|
-
|
|
35
|
-
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
36
|
-
|
|
37
|
-
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
38
|
-
|
|
39
|
-
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
40
|
-
|
|
41
|
-
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
42
|
-
|
|
43
|
-
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
44
|
-
|
|
45
|
-
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
46
|
-
|
|
47
|
-
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
48
|
-
|
|
49
|
-
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
50
|
-
|
|
51
|
-
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
52
|
-
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
53
|
-
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
54
|
-
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
|
55
|
-
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
56
|
-
|
|
57
|
-
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
58
|
-
|
|
59
|
-
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
60
|
-
|
|
61
|
-
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
62
|
-
|
|
63
|
-
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
64
|
-
|
|
65
|
-
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
66
|
-
|
|
1
|
+
Copyright (c) 2026 Majikah Solutions OPC
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Apache License
|
|
18
|
+
Version 2.0, January 2004
|
|
19
|
+
http://www.apache.org/licenses/
|
|
20
|
+
|
|
21
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
22
|
+
|
|
23
|
+
1. Definitions.
|
|
24
|
+
|
|
25
|
+
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
26
|
+
|
|
27
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
28
|
+
|
|
29
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
30
|
+
|
|
31
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
32
|
+
|
|
33
|
+
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
34
|
+
|
|
35
|
+
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
36
|
+
|
|
37
|
+
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
40
|
+
|
|
41
|
+
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
42
|
+
|
|
43
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
44
|
+
|
|
45
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
46
|
+
|
|
47
|
+
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
48
|
+
|
|
49
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
50
|
+
|
|
51
|
+
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
52
|
+
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
53
|
+
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
54
|
+
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
|
55
|
+
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
56
|
+
|
|
57
|
+
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
58
|
+
|
|
59
|
+
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
60
|
+
|
|
61
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
62
|
+
|
|
63
|
+
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
64
|
+
|
|
65
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
66
|
+
|
|
67
67
|
END OF TERMS AND CONDITIONS
|
package/README.md
CHANGED
|
@@ -1,74 +1,153 @@
|
|
|
1
1
|
# Majik SLink
|
|
2
2
|
|
|
3
|
-
[](https://thezelijah.world) 
|
|
3
|
+
[](https://www.thezelijah.world) 
|
|
4
|
+
  [](https://opensource.org/licenses/Apache-2.0) 
|
|
4
5
|
|
|
5
|
-
**MajikSLink** is a specialized TypeScript library designed for URL ownership
|
|
6
|
+
**MajikSLink** is a specialized TypeScript library designed for URL binding, ownership, and cryptographic attribution. It provides a robust, zero-trust mechanism to prove that a specific digital identity (via a **MajikKey** and **MUID**) controls or is attested to a publicly accessible web resource (such as a YouTube channel, social media profile, DNS record, or personal blog).
|
|
6
7
|
|
|
7
|
-
By combining traditional **Ed25519** signatures with post-quantum **ML-DSA-87
|
|
8
|
+
By combining traditional **Ed25519** signatures with post-quantum **ML-DSA-87 (Dilithium)** hybrid cryptography via `@majikah/majik-signature`, MajikSLink ensures that your identity-to-URL associations are tamper-proof, resilient against future quantum threats, and structurally decoupled for decentralized verification.
|
|
8
9
|
|
|
10
|
+
Identities can be created and managed via the web application at **[https://id.majikah.solutions](https://id.majikah.solutions)**.
|
|
9
11
|
|
|
12
|
+
---
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
## Key Features
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
1. **Deterministic URL Normalization:** Automatically converts URLs into a canonical format (`majik-slink-v1:<subdomain>::<sld>::<tld>::<path>`). It strips tracking queries, fragments, and trailing slashes so the exact same core resource always generates a consistent hash and verification code.
|
|
17
|
+
2. **Flexible Claim Types:**
|
|
18
|
+
* **`ownership`**: The strongest claim, indicating domain control. Verified via DNS TXT records.
|
|
19
|
+
* **`attribution`**: Indicates content control (e.g., a channel bio or GitHub README) but not DNS control. Verified via page content scraping.
|
|
20
|
+
* **`reference`**: A pure cryptographic attestation ("this URL is relevant to me") with no independent third-party verification possible.
|
|
21
|
+
3. **Compact Post-Quantum Signatures:** Signatures are stored in a compact envelope. Public keys are *never* embedded directly, requiring verifiers to externally resolve them via the signer's `muid`, ensuring up-to-date registry checks.
|
|
22
|
+
4. **Challenge-Response Verification:** Derives a short OTP-style code (`vCode`) from the SHA-256 hash of the canonical URL to be placed in DNS TXT records or page bios.
|
|
23
|
+
5. **Serialization Ready:** Built-in support for flawless JSON rehydration (`toJSON` / `fromJSON`) and Base64 serialization (`serialize` / `deserialize`).
|
|
14
24
|
|
|
25
|
+
---
|
|
15
26
|
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @majikah/majik-slink @majikah/majik-key @majikah/majik-signature
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
16
34
|
|
|
35
|
+
## Quick Start & Usage
|
|
17
36
|
|
|
18
|
-
|
|
19
|
-
|
|
37
|
+
### 1. Generating a UI Challenge (Without Signing)
|
|
38
|
+
If you are building a UI and want to show the user their verification code *before* they confirm and sign, you can generate a challenge using just the URL.
|
|
20
39
|
|
|
21
|
-
|
|
40
|
+
```typescript
|
|
41
|
+
import { MajikSLink } from "@majikah/majik-slink";
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
const preview = await MajikSLink.generateChallenge("https://github.com/majikah/repo?utm_source=test#readme");
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
if (preview) {
|
|
46
|
+
console.log("Canonical URL:", preview.cleanUrl); // https://github.com/majikah/repo
|
|
47
|
+
console.log("Ask user to add this to their bio:", preview.v_code); // majik-slink:xxxx...
|
|
48
|
+
}
|
|
49
|
+
```
|
|
26
50
|
|
|
27
|
-
|
|
51
|
+
### 2. Creating and Signing a New SLink
|
|
52
|
+
To permanently link an identity to a URL, create a `MajikSLink` instance using an unlocked `MajikKey`.
|
|
28
53
|
|
|
54
|
+
```typescript
|
|
55
|
+
import { MajikKey } from "@majikah/majik-key";
|
|
29
56
|
|
|
30
|
-
|
|
57
|
+
// 1. Initialize and unlock your identity
|
|
58
|
+
const mnemonic = MajikKey.generateMnemonic();
|
|
59
|
+
const key = await MajikKey.create(mnemonic, 'my-passphrase', 'My Signing Key');
|
|
60
|
+
const userId = "user_abc123";
|
|
61
|
+
const muid = "muid_xyz987";
|
|
31
62
|
|
|
63
|
+
// 2. Create the SLink payload
|
|
64
|
+
const targetUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
|
|
32
65
|
|
|
33
|
-
|
|
34
|
-
|
|
66
|
+
const slink = await MajikSLink.create(targetUrl, key, userId, muid, {
|
|
67
|
+
claimType: "attribution" // Automatically defaults verificationMethod to "page_content"
|
|
68
|
+
});
|
|
35
69
|
|
|
36
|
-
|
|
70
|
+
console.log("Status:", slink.status); // "unverified"
|
|
71
|
+
console.log("Source Platform:", slink.source); // "youtube"
|
|
72
|
+
```
|
|
37
73
|
|
|
38
|
-
|
|
74
|
+
### 3. Displaying Verification Requirements
|
|
75
|
+
Depending on the `claimType`, you can guide users on how to prove their link:
|
|
39
76
|
|
|
77
|
+
```typescript
|
|
78
|
+
if (slink.verificationMethod === "dns_txt") {
|
|
79
|
+
console.log("Add a TXT Record:");
|
|
80
|
+
console.log("Name:", slink.dnsRecordName); // _majik-challenge.youtube.com
|
|
81
|
+
console.log("Value:", slink.dnsRecordValue); // majik-slink-verify=majik-slink:a1b2...
|
|
82
|
+
} else if (slink.verificationMethod === "page_content") {
|
|
83
|
+
console.log("Add this code anywhere in your bio or README:");
|
|
84
|
+
console.log(slink.vCode); // majik-slink:a1b2...
|
|
85
|
+
|
|
86
|
+
// You can also display it in nice OTP chunks:
|
|
87
|
+
console.log("Display Code:", slink.codeChunks.join("-"));
|
|
88
|
+
}
|
|
89
|
+
```
|
|
40
90
|
|
|
91
|
+
### 4. Verifying Cryptographic Signatures
|
|
92
|
+
Because MajikSLink uses **compact signatures**, public keys are not stored in the SLink payload. You must fetch the public keys (via `muid`) from your trusted identity registry to verify the claim.
|
|
41
93
|
|
|
42
|
-
|
|
43
|
-
|
|
94
|
+
```typescript
|
|
95
|
+
import { MajikSignature } from "@majikah/majik-signature";
|
|
44
96
|
|
|
45
|
-
|
|
97
|
+
// 1. Fetch public keys from your trusted registry using the SLink's MUID
|
|
98
|
+
const publicKeys = await fetchPublicKeysForMuid(slink.muid);
|
|
46
99
|
|
|
47
|
-
|
|
100
|
+
// 2. Verify the cryptographic signature against the canonical URL
|
|
101
|
+
const verificationResult = slink.verify(publicKeys);
|
|
48
102
|
|
|
103
|
+
if (verificationResult.valid) {
|
|
104
|
+
console.log("Valid signature from:", verificationResult.signerId);
|
|
105
|
+
|
|
106
|
+
// 3. (Optional) If you also successfully scraped the vCode from the web:
|
|
107
|
+
slink.markVerified();
|
|
108
|
+
} else {
|
|
109
|
+
slink.markSignatureInvalid();
|
|
110
|
+
}
|
|
111
|
+
```
|
|
49
112
|
|
|
50
|
-
|
|
51
|
-
Seed phrase account library — required peer dependency for signing and encryption.
|
|
113
|
+
*Note: You can also verify statically without hydrating via `MajikSLink.verifySignature(slinkJSON, publicKeys)`.*
|
|
52
114
|
|
|
53
|
-
|
|
115
|
+
### 5. Utilities & Comparisons
|
|
116
|
+
Easily compare URLs to see if they resolve to the exact same signed resource:
|
|
54
117
|
|
|
55
|
-
|
|
56
|
-
|
|
118
|
+
```typescript
|
|
119
|
+
// Ignores query params and fragments automatically
|
|
120
|
+
const isMatch = slink.isSameResource("https://www.youtube.com/watch?v=dQw4w9WgXcQ&t=42s");
|
|
121
|
+
console.log(isMatch); // true
|
|
57
122
|
|
|
58
|
-
|
|
123
|
+
// View a human-readable debug summary
|
|
124
|
+
console.log(slink.toSummary());
|
|
125
|
+
```
|
|
59
126
|
|
|
127
|
+
### 6. Serialization and Database Storage
|
|
128
|
+
Store and retrieve `MajikSLink` objects seamlessly.
|
|
60
129
|
|
|
61
|
-
|
|
62
|
-
|
|
130
|
+
```typescript
|
|
131
|
+
// Export to JSON for Database storage
|
|
132
|
+
const jsonPayload = slink.toJSON();
|
|
63
133
|
|
|
64
|
-
|
|
134
|
+
// Hydrate from DB (Note: signatures must still be verified with external keys)
|
|
135
|
+
const hydratedSLink = MajikSLink.fromJSON(jsonPayload);
|
|
65
136
|
|
|
137
|
+
// Export to Base64 (Useful for QR codes or URL parameters)
|
|
138
|
+
const base64String = slink.serialize();
|
|
139
|
+
const fromBase64 = MajikSLink.deserialize(base64String);
|
|
140
|
+
```
|
|
66
141
|
|
|
67
142
|
---
|
|
68
143
|
|
|
69
|
-
##
|
|
144
|
+
## Related Ecosystem Projects
|
|
70
145
|
|
|
71
|
-
|
|
146
|
+
* **[Majik Signature](https://www.npmjs.com/package/@majikah/majik-signature)**: Hybrid post-quantum content signing engine. ([Microsoft Store](https://apps.microsoft.com/detail/9pl9g3xzvd1x))
|
|
147
|
+
* **[Majik Message](https://message.majikah.solutions)**: Secure messaging platform utilizing Majik Keys and hybrid ML-KEM-768/X25519 protocols for identity-bound communication. ([Microsoft Store](https://apps.microsoft.com/detail/9pmjgvzzjspn))
|
|
148
|
+
* **[Majik Key](https://www.npmjs.com/package/@majikah/majik-key)**: Foundational seed phrase account library.
|
|
149
|
+
* **[Majik Universal ID](https://id.majikah.solutions)**: A cryptographically anchored identity layer for the Majikah ecosystem.
|
|
150
|
+
* **[Majik Envelope](https://www.npmjs.com/package/@majikah/majik-envelope)**: Post-quantum group encryption.
|
|
72
151
|
|
|
73
152
|
---
|
|
74
153
|
|
|
@@ -80,16 +159,16 @@ If you want to contribute or help extend support, reach out via email. All contr
|
|
|
80
159
|
|
|
81
160
|
## Author
|
|
82
161
|
|
|
83
|
-
|
|
162
|
+
Developed by **Josef Elijah Fabian (Zelijah)** | [Majikah Solutions OPC](https://majikah.solutions/about)
|
|
84
163
|
|
|
85
|
-
**Developer**: Josef Elijah Fabian
|
|
86
|
-
**GitHub**: [https://github.com/
|
|
87
|
-
**Project Repository**: [https://github.com/Majikah/majik-slink](https://github.com/Majikah/majik-slink)
|
|
164
|
+
**Developer**: [Josef Elijah Fabian](https://github.com/jedlsf)
|
|
165
|
+
**GitHub**: [https://github.com/Majikah](https://github.com/Majikah)
|
|
166
|
+
**Project Repository**: [https://github.com/Majikah/majik-slink](https://github.com/Majikah/majik-slink)
|
|
88
167
|
|
|
89
168
|
---
|
|
90
169
|
|
|
91
170
|
## Contact
|
|
92
171
|
|
|
93
|
-
- **Business Email**: [business@
|
|
172
|
+
- **Business Email**: [business@majikah.solutions](mailto:business@majikah.solutions)
|
|
94
173
|
- **Official Website**: [https://www.thezelijah.world](https://www.thezelijah.world)
|
|
95
|
-
- **
|
|
174
|
+
- **Majikah Ecosystem**: [https://majikah.solutions](https://majikah.solutions)
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,26 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
canonical: string;
|
|
20
|
-
}
|
|
1
|
+
import { ISODateString } from "@majikah/majik-key";
|
|
2
|
+
import { MajikSignatureCompactJSON } from "@majikah/majik-signature";
|
|
3
|
+
/**
|
|
4
|
+
* What kind of relationship the user claims to have with this URL.
|
|
5
|
+
* - "ownership": user controls the domain (can add a DNS TXT record).
|
|
6
|
+
* Strongest proof; independent of any single page's content.
|
|
7
|
+
* - "attribution": user controls the *content* at this URL (a profile bio,
|
|
8
|
+
* video description, repo README) but not the domain/DNS
|
|
9
|
+
* itself — e.g. a YouTube channel, a GitHub profile.
|
|
10
|
+
* Provable only via page-embed code, never DNS.
|
|
11
|
+
* - "reference": user controls neither the domain nor the content.
|
|
12
|
+
* A pure attestation ("this URL is about me / relevant to
|
|
13
|
+
* me") with no independent proof possible. Should never
|
|
14
|
+
* be presented to end users as "verified" — surface it
|
|
15
|
+
* as "attested by <signer>" instead.
|
|
16
|
+
*/
|
|
17
|
+
export type SLinkClaimType = "ownership" | "attribution" | "reference";
|
|
18
|
+
export type SLinkVerificationMethod = "dns_txt" | "page_content" | null;
|
|
21
19
|
/** Verification status of a MajikSLink */
|
|
22
20
|
export type SLinkVerificationStatus = "pending" | "unverified" | "verified" | "failed" | "sig_invalid";
|
|
23
|
-
/** Shape persisted to / rehydrated from a database */
|
|
24
21
|
export interface MajikSLinkJSON {
|
|
25
22
|
version: 1;
|
|
26
23
|
id: string;
|
|
@@ -40,14 +37,35 @@ export interface MajikSLinkJSON {
|
|
|
40
37
|
v_code: string;
|
|
41
38
|
/** Detected source platform */
|
|
42
39
|
source: SLinkSource;
|
|
40
|
+
claim_type: SLinkClaimType;
|
|
41
|
+
verification_method: SLinkVerificationMethod;
|
|
43
42
|
/** Verification status */
|
|
44
43
|
status: SLinkVerificationStatus;
|
|
45
44
|
/** ISO timestamp of when this SLink was created */
|
|
46
|
-
timestamp:
|
|
45
|
+
timestamp: ISODateString;
|
|
47
46
|
/** ISO timestamp of the last verification attempt, or null */
|
|
48
|
-
verified_at:
|
|
49
|
-
/**
|
|
50
|
-
signature:
|
|
47
|
+
verified_at: ISODateString | null;
|
|
48
|
+
/** Compact envelope — no embedded public keys. Resolve via muid/signerId at verify time. */
|
|
49
|
+
signature: MajikSignatureCompactJSON;
|
|
50
|
+
}
|
|
51
|
+
/** Source platform of the signed URL */
|
|
52
|
+
export type SLinkSource = "youtube" | "x" | "instagram" | "tiktok" | "github" | "website" | "other";
|
|
53
|
+
/** Structured URL parts extracted during normalisation */
|
|
54
|
+
export interface UrlInfo {
|
|
55
|
+
/** Full registered domain e.g. "youtube.com" */
|
|
56
|
+
domain: string;
|
|
57
|
+
/** Second-level domain only e.g. "youtube" */
|
|
58
|
+
sld: string;
|
|
59
|
+
/** Top-level domain e.g. "com" */
|
|
60
|
+
tld: string;
|
|
61
|
+
/** Subdomain or null e.g. "www" | null */
|
|
62
|
+
subdomain: string | null;
|
|
63
|
+
/** Normalised pathname e.g. "/watch/dQw4w9WgXcQ" */
|
|
64
|
+
path: string;
|
|
65
|
+
/** Reconstructed clean URL (no params, no fragment) */
|
|
66
|
+
cleanUrl: string;
|
|
67
|
+
/** The string that is actually hashed + signed */
|
|
68
|
+
canonical: string;
|
|
51
69
|
}
|
|
52
70
|
/** Lightweight code-only payload — useful for the UI before full create() */
|
|
53
71
|
export interface SLinkCodePreview {
|
|
@@ -60,3 +78,25 @@ export interface SLinkCodePreview {
|
|
|
60
78
|
/** Parsed URL parts */
|
|
61
79
|
urlInfo: UrlInfo;
|
|
62
80
|
}
|
|
81
|
+
export interface MajikSLinkConstructorOptions {
|
|
82
|
+
version: 1;
|
|
83
|
+
id: string;
|
|
84
|
+
user_id: string;
|
|
85
|
+
muid: string;
|
|
86
|
+
domain: string;
|
|
87
|
+
sld: string;
|
|
88
|
+
tld: string;
|
|
89
|
+
subdomain: string | null;
|
|
90
|
+
path: string;
|
|
91
|
+
url: string;
|
|
92
|
+
clean_url: string;
|
|
93
|
+
hash: string;
|
|
94
|
+
v_code: string;
|
|
95
|
+
source: SLinkSource;
|
|
96
|
+
claim_type?: SLinkClaimType;
|
|
97
|
+
verification_method?: SLinkVerificationMethod;
|
|
98
|
+
status: SLinkVerificationStatus;
|
|
99
|
+
verified_at: Date | null;
|
|
100
|
+
signature: MajikSignatureCompactJSON;
|
|
101
|
+
timestamp: Date;
|
|
102
|
+
}
|
package/dist/core/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MajikKey } from "@majikah/majik-key";
|
|
2
|
-
import { SLinkSource, UrlInfo } from "./types";
|
|
2
|
+
import { SLinkClaimType, SLinkSource, SLinkVerificationMethod, UrlInfo } from "./types";
|
|
3
3
|
export declare function sha256Hex(input: string): Promise<string>;
|
|
4
4
|
export declare function generateId(): string;
|
|
5
5
|
/**
|
|
@@ -22,3 +22,11 @@ export declare function parseUrlInfo(rawUrl: string): UrlInfo | null;
|
|
|
22
22
|
* Used during fromJSON rehydration to avoid re-running PSL.
|
|
23
23
|
*/
|
|
24
24
|
export declare function buildCanonical(subdomain: string | null, sld: string, tld: string, path: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Derive the natural default verification method for a claim type when the
|
|
27
|
+
* caller doesn't specify one explicitly.
|
|
28
|
+
* - "ownership" → "dns_txt" (strongest available proof for a domain the signer controls)
|
|
29
|
+
* - "attribution" → "page_content" (only proof possible without domain control)
|
|
30
|
+
* - "reference" → null (no independent proof is possible at all)
|
|
31
|
+
*/
|
|
32
|
+
export declare function defaultVerificationMethod(claimType: SLinkClaimType): SLinkVerificationMethod;
|
package/dist/core/utils.js
CHANGED
|
@@ -118,3 +118,20 @@ export function parseUrlInfo(rawUrl) {
|
|
|
118
118
|
export function buildCanonical(subdomain, sld, tld, path) {
|
|
119
119
|
return `${CANONICAL_PREFIX}${subdomain ?? "root"}::${sld}::${tld}::${path}`;
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* Derive the natural default verification method for a claim type when the
|
|
123
|
+
* caller doesn't specify one explicitly.
|
|
124
|
+
* - "ownership" → "dns_txt" (strongest available proof for a domain the signer controls)
|
|
125
|
+
* - "attribution" → "page_content" (only proof possible without domain control)
|
|
126
|
+
* - "reference" → null (no independent proof is possible at all)
|
|
127
|
+
*/
|
|
128
|
+
export function defaultVerificationMethod(claimType) {
|
|
129
|
+
switch (claimType) {
|
|
130
|
+
case "ownership":
|
|
131
|
+
return "dns_txt";
|
|
132
|
+
case "attribution":
|
|
133
|
+
return "page_content";
|
|
134
|
+
case "reference":
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,4 +6,4 @@ export { MajikSLink } from "./majik-slink";
|
|
|
6
6
|
export type * from "./core/types";
|
|
7
7
|
export * from "./core/errors";
|
|
8
8
|
export * from "./core/constants";
|
|
9
|
-
export { parseUrlInfo, buildCanonical, detectSource } from "./core/utils";
|
|
9
|
+
export { parseUrlInfo, buildCanonical, detectSource, defaultVerificationMethod, } from "./core/utils";
|
package/dist/index.js
CHANGED
|
@@ -9,4 +9,4 @@ export * from "./core/errors";
|
|
|
9
9
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
10
10
|
export * from "./core/constants";
|
|
11
11
|
// ── Low-level utilities (opt-in) ──────────────────────────────────────────────
|
|
12
|
-
export { parseUrlInfo, buildCanonical, detectSource } from "./core/utils";
|
|
12
|
+
export { parseUrlInfo, buildCanonical, detectSource, defaultVerificationMethod, } from "./core/utils";
|
package/dist/majik-slink.d.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* majik-slink.ts
|
|
3
3
|
*/
|
|
4
4
|
import type { MajikKey } from "@majikah/majik-key";
|
|
5
|
-
import { MajikSignature,
|
|
6
|
-
import { MajikSLinkJSON, SLinkCodePreview, SLinkSource, SLinkVerificationStatus, UrlInfo } from "./core/types";
|
|
5
|
+
import { MajikSignature, MajikSignatureCompactJSON, MajikSignerPublicKeys, VerificationResult } from "@majikah/majik-signature";
|
|
6
|
+
import { MajikSLinkJSON, SLinkClaimType, SLinkCodePreview, SLinkSource, SLinkVerificationMethod, SLinkVerificationStatus, UrlInfo } from "./core/types";
|
|
7
7
|
/**
|
|
8
8
|
* MajikSLink
|
|
9
9
|
* ---------------------
|
|
10
|
-
* MajikSLink — URL ownership
|
|
11
|
-
* A MajikSLink
|
|
10
|
+
* MajikSLink — URL binding, ownership, and attribution signing/verification.
|
|
11
|
+
* A MajikSLink cryptographically associates a MUID/MajikKey with a
|
|
12
12
|
* publicly-accessible URL by:
|
|
13
13
|
*
|
|
14
14
|
* 1. Normalising the URL to a canonical form (scheme + subdomain + domain +
|
|
@@ -16,13 +16,28 @@ import { MajikSLinkJSON, SLinkCodePreview, SLinkSource, SLinkVerificationStatus,
|
|
|
16
16
|
* 2. Deriving a short, deterministic verification code from a SHA-256 digest
|
|
17
17
|
* of the canonical URL parts — so the same resource always produces the
|
|
18
18
|
* same code regardless of query params or tracking tokens.
|
|
19
|
-
* 3. Signing the canonical URL string with the
|
|
20
|
-
* MajikSignature (Ed25519 + ML-DSA-87 hybrid)
|
|
19
|
+
* 3. Signing the canonical URL string with the signer's MajikKey via
|
|
20
|
+
* MajikSignature (Ed25519 + ML-DSA-87 hybrid), stored in compact form
|
|
21
|
+
* (no embedded public keys — resolved externally at verify time via
|
|
22
|
+
* muid / signerId).
|
|
21
23
|
*
|
|
22
|
-
* The
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the signer
|
|
24
|
+
* The *strength* of the claim is described by `claimType`:
|
|
25
|
+
* - "ownership": the signer controls the domain. Verifiable via DNS TXT
|
|
26
|
+
* (strongest — survives page changes) or page content.
|
|
27
|
+
* - "attribution": the signer controls the content at this URL (a channel
|
|
28
|
+
* bio, repo README) but not the domain/DNS. Verifiable
|
|
29
|
+
* only via page content.
|
|
30
|
+
* - "reference": the signer controls neither. A signed attestation with
|
|
31
|
+
* no independent proof possible — never present this to
|
|
32
|
+
* end users as "verified"; surface it as "attested by
|
|
33
|
+
* <signer>" instead.
|
|
34
|
+
* See `SLinkClaimType` / `SLinkVerificationMethod` for the full contract.
|
|
35
|
+
*
|
|
36
|
+
* For "dns_txt" verification, the owner publishes `dnsRecordValue` at
|
|
37
|
+
* `dnsRecordName`. For "page_content", the owner embeds `vCode` anywhere in
|
|
38
|
+
* the public page (description, bio, comment, HTML body) and a scraper
|
|
39
|
+
* verifies its presence. Once found, the MajikSignature certifies the
|
|
40
|
+
* association between the signer's identity and the URL.
|
|
26
41
|
*
|
|
27
42
|
* Canonical URL format (what is signed):
|
|
28
43
|
* "majik-slink-v1:<subdomain>::<sld>::<tld>::<path>"
|
|
@@ -49,8 +64,10 @@ export declare class MajikSLink {
|
|
|
49
64
|
private readonly _source;
|
|
50
65
|
private _status;
|
|
51
66
|
private _verified_at;
|
|
67
|
+
private readonly _claim_type;
|
|
68
|
+
private readonly _verification_method;
|
|
52
69
|
private readonly _signature;
|
|
53
|
-
private
|
|
70
|
+
private _resolvedSignature;
|
|
54
71
|
private readonly _timestamp;
|
|
55
72
|
private constructor();
|
|
56
73
|
get version(): 1;
|
|
@@ -79,20 +96,59 @@ export declare class MajikSLink {
|
|
|
79
96
|
get status(): SLinkVerificationStatus;
|
|
80
97
|
get verifiedAt(): Date | null;
|
|
81
98
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
99
|
+
* The resolved MajikSignature instance, if `resolveSignature()` has been
|
|
100
|
+
* called. Null until then — MajikSLink stores only the compact envelope
|
|
101
|
+
* (no embedded public keys), so there is nothing to eagerly resolve.
|
|
102
|
+
*/
|
|
103
|
+
get signature(): MajikSignature | null;
|
|
104
|
+
/**
|
|
105
|
+
* Get the raw compact signature JSON as stored/persisted.
|
|
106
|
+
* No public keys are embedded — resolve them externally (e.g. by
|
|
107
|
+
* `signerId` / `muid` from your key registry) before verifying.
|
|
84
108
|
*/
|
|
85
|
-
get
|
|
109
|
+
get signatureJSON(): MajikSignatureCompactJSON;
|
|
86
110
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
111
|
+
* Resolve the compact signature into a full MajikSignature instance, using
|
|
112
|
+
* externally-supplied public keys (looked up by `this.muid` or
|
|
113
|
+
* `this.signatureJSON.signerId` from your key registry).
|
|
114
|
+
*
|
|
115
|
+
* There is nothing to fall back to by design — MajikSLink never stores
|
|
116
|
+
* public keys inline, so `publicKeys` is mandatory. The result is cached;
|
|
117
|
+
* subsequent calls with the same (correct) keys are free.
|
|
118
|
+
*
|
|
119
|
+
* @throws {MajikSLinkValidationError} if `publicKeys.signerId` does not
|
|
120
|
+
* match the signerId recorded on this SLink's signature.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* const keys = await resolvePublicKeysForMuid(slink.muid);
|
|
124
|
+
* const sig = slink.resolveSignature(keys);
|
|
125
|
+
* console.log(sig.hasTSA);
|
|
89
126
|
*/
|
|
90
|
-
|
|
127
|
+
resolveSignature(publicKeys: MajikSignerPublicKeys): MajikSignature;
|
|
128
|
+
get claimType(): SLinkClaimType;
|
|
129
|
+
get verificationMethod(): SLinkVerificationMethod;
|
|
91
130
|
get timestamp(): Date;
|
|
92
131
|
/** True when a successful scrape + signature verification has been recorded */
|
|
93
132
|
get isVerified(): boolean;
|
|
94
133
|
/** The OTP-display chunks (groups of 4 hex chars from the hash) */
|
|
95
134
|
get codeChunks(): string[];
|
|
135
|
+
/**
|
|
136
|
+
* The DNS TXT record name to publish for `"dns_txt"` verification.
|
|
137
|
+
* Only meaningful when `verificationMethod === "dns_txt"` — for
|
|
138
|
+
* `"page_content"` or `"reference"` SLinks this value is simply
|
|
139
|
+
* irrelevant (not an error), so check `verificationMethod` before using it.
|
|
140
|
+
*
|
|
141
|
+
* @example "_majik-challenge.example.com"
|
|
142
|
+
*/
|
|
143
|
+
get dnsRecordName(): string;
|
|
144
|
+
/**
|
|
145
|
+
* The DNS TXT record value to publish for `"dns_txt"` verification.
|
|
146
|
+
* See `dnsRecordName` — only meaningful when
|
|
147
|
+
* `verificationMethod === "dns_txt"`.
|
|
148
|
+
*
|
|
149
|
+
* @example "majik-slink-verify=majik-slink:a1b2c3d4..."
|
|
150
|
+
*/
|
|
151
|
+
get dnsRecordValue(): string;
|
|
96
152
|
/**
|
|
97
153
|
* Create and sign a new MajikSLink.
|
|
98
154
|
*
|
|
@@ -119,6 +175,8 @@ export declare class MajikSLink {
|
|
|
119
175
|
id?: string;
|
|
120
176
|
timestamp?: Date;
|
|
121
177
|
status?: SLinkVerificationStatus;
|
|
178
|
+
claimType?: SLinkClaimType;
|
|
179
|
+
verificationMethod?: SLinkVerificationMethod;
|
|
122
180
|
}): Promise<MajikSLink>;
|
|
123
181
|
/**
|
|
124
182
|
* Derive the verification code and URL info for a raw URL *without* signing.
|
|
@@ -135,43 +193,53 @@ export declare class MajikSLink {
|
|
|
135
193
|
*/
|
|
136
194
|
static generateChallenge(rawUrl: string): Promise<SLinkCodePreview | null>;
|
|
137
195
|
/**
|
|
138
|
-
* Verify the
|
|
196
|
+
* Verify the stored MajikSignature of a persisted MajikSLink against
|
|
197
|
+
* externally-supplied public keys.
|
|
139
198
|
*
|
|
140
|
-
* This does NOT scrape the target page — it only
|
|
141
|
-
* that the signature covers the canonical URL
|
|
142
|
-
*
|
|
199
|
+
* This does NOT scrape the target page or check DNS — it only
|
|
200
|
+
* cryptographically checks that the signature covers the canonical URL
|
|
201
|
+
* and was issued by the holder of `publicKeys`. No public keys are
|
|
202
|
+
* embedded in the stored SLink; you must resolve them yourself (e.g. by
|
|
203
|
+
* `signerId` / `muid` from your key registry) before calling this.
|
|
143
204
|
*
|
|
144
|
-
* Use in conjunction with your
|
|
145
|
-
* 1.
|
|
205
|
+
* Use in conjunction with your DNS/scrape check to fully verify a claim:
|
|
206
|
+
* 1. Look up (dns_txt) or scrape (page_content) per `verificationMethod`
|
|
207
|
+
* and confirm `v_code` is present.
|
|
146
208
|
* 2. Call `verifySignature()` to confirm the cryptographic provenance.
|
|
147
209
|
*
|
|
148
210
|
* @example
|
|
149
|
-
* const
|
|
150
|
-
*
|
|
211
|
+
* const keys = await resolvePublicKeysForMuid(slink.muid);
|
|
212
|
+
* const result = MajikSLink.verifySignature(slink, keys);
|
|
213
|
+
* if (!result.valid) console.warn("Signature invalid:", result.reason);
|
|
151
214
|
*/
|
|
152
|
-
static verifySignature(slink: MajikSLink | MajikSLinkJSON, publicKeys:
|
|
215
|
+
static verifySignature(slink: MajikSLink | MajikSLinkJSON, publicKeys: MajikSignerPublicKeys): VerificationResult;
|
|
153
216
|
/**
|
|
154
|
-
* Verify this SLink's
|
|
217
|
+
* Verify this SLink's stored signature against externally-supplied
|
|
218
|
+
* public keys.
|
|
155
219
|
*
|
|
156
220
|
* This validates that:
|
|
157
221
|
* 1. The signature cryptographically matches the canonical URL
|
|
158
222
|
* 2. The signature was created by the holder of the private key
|
|
223
|
+
* corresponding to `publicKeys`
|
|
159
224
|
*
|
|
160
|
-
* Note: This does NOT scrape the page. Use with
|
|
161
|
-
* a successful
|
|
225
|
+
* Note: This does NOT scrape the page or check DNS. Use with
|
|
226
|
+
* `markVerified()` after a successful DNS lookup / scrape (per
|
|
227
|
+
* `verificationMethod`) to complete the verification flow.
|
|
162
228
|
*
|
|
163
|
-
* @param publicKeys The signer's public keys (classic Ed25519 + ML-DSA-87)
|
|
229
|
+
* @param publicKeys The signer's public keys (classic Ed25519 + ML-DSA-87),
|
|
230
|
+
* resolved externally — e.g. by `this.muid`.
|
|
164
231
|
* @returns Verification result with valid/invalid status and optional reason
|
|
165
232
|
*
|
|
166
233
|
* @example
|
|
167
|
-
* // After
|
|
168
|
-
* const
|
|
234
|
+
* // After confirming v_code via DNS or page scrape:
|
|
235
|
+
* const keys = await resolvePublicKeysForMuid(slink.muid);
|
|
236
|
+
* const result = slink.verify(keys);
|
|
169
237
|
* if (result.valid) {
|
|
170
238
|
* slink.markVerified();
|
|
171
239
|
* await db.save(slink);
|
|
172
240
|
* }
|
|
173
241
|
*/
|
|
174
|
-
verify(publicKeys
|
|
242
|
+
verify(publicKeys: MajikSignerPublicKeys): VerificationResult;
|
|
175
243
|
/**
|
|
176
244
|
* Record a successful scrape verification.
|
|
177
245
|
* Should be called by your server route after a positive scrape result.
|
|
@@ -208,7 +276,8 @@ export declare class MajikSLink {
|
|
|
208
276
|
toJSON(): MajikSLinkJSON;
|
|
209
277
|
/**
|
|
210
278
|
* Rehydrate a MajikSLink from a plain JSON object (e.g. from a database row).
|
|
211
|
-
* Does NOT re-verify the signature — call `verify()` explicitly
|
|
279
|
+
* Does NOT re-verify the signature — call `verify()` explicitly with
|
|
280
|
+
* externally-resolved public keys.
|
|
212
281
|
*
|
|
213
282
|
* @throws {MajikSLinkSerializationError} if any required field is missing or malformed.
|
|
214
283
|
*/
|
package/dist/majik-slink.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* majik-slink.ts
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { MajikSignature, } from "@majikah/majik-signature";
|
|
5
5
|
import { MajikSLinkError, MajikSLinkSerializationError, MajikSLinkSigningError, MajikSLinkValidationError, } from "./core/errors";
|
|
6
6
|
import { CODE_HEX_LENGTH, CODE_PREFIX, SLINK_VERSION } from "./core/constants";
|
|
7
|
-
import { assertMajikKey, assertNonEmptyString, assertUnlockedKey, assertValidHttpUrl, buildCanonical, detectSource, generateId, parseUrlInfo, sha256Hex, } from "./core/utils";
|
|
7
|
+
import { assertMajikKey, assertNonEmptyString, assertUnlockedKey, assertValidHttpUrl, buildCanonical, defaultVerificationMethod, detectSource, generateId, parseUrlInfo, sha256Hex, } from "./core/utils";
|
|
8
8
|
// ─── MajikSLink ───────────────────────────────────────────────────────────────
|
|
9
9
|
/**
|
|
10
10
|
* MajikSLink
|
|
11
11
|
* ---------------------
|
|
12
|
-
* MajikSLink — URL ownership
|
|
13
|
-
* A MajikSLink
|
|
12
|
+
* MajikSLink — URL binding, ownership, and attribution signing/verification.
|
|
13
|
+
* A MajikSLink cryptographically associates a MUID/MajikKey with a
|
|
14
14
|
* publicly-accessible URL by:
|
|
15
15
|
*
|
|
16
16
|
* 1. Normalising the URL to a canonical form (scheme + subdomain + domain +
|
|
@@ -18,13 +18,28 @@ import { assertMajikKey, assertNonEmptyString, assertUnlockedKey, assertValidHtt
|
|
|
18
18
|
* 2. Deriving a short, deterministic verification code from a SHA-256 digest
|
|
19
19
|
* of the canonical URL parts — so the same resource always produces the
|
|
20
20
|
* same code regardless of query params or tracking tokens.
|
|
21
|
-
* 3. Signing the canonical URL string with the
|
|
22
|
-
* MajikSignature (Ed25519 + ML-DSA-87 hybrid)
|
|
21
|
+
* 3. Signing the canonical URL string with the signer's MajikKey via
|
|
22
|
+
* MajikSignature (Ed25519 + ML-DSA-87 hybrid), stored in compact form
|
|
23
|
+
* (no embedded public keys — resolved externally at verify time via
|
|
24
|
+
* muid / signerId).
|
|
23
25
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* the signer
|
|
26
|
+
* The *strength* of the claim is described by `claimType`:
|
|
27
|
+
* - "ownership": the signer controls the domain. Verifiable via DNS TXT
|
|
28
|
+
* (strongest — survives page changes) or page content.
|
|
29
|
+
* - "attribution": the signer controls the content at this URL (a channel
|
|
30
|
+
* bio, repo README) but not the domain/DNS. Verifiable
|
|
31
|
+
* only via page content.
|
|
32
|
+
* - "reference": the signer controls neither. A signed attestation with
|
|
33
|
+
* no independent proof possible — never present this to
|
|
34
|
+
* end users as "verified"; surface it as "attested by
|
|
35
|
+
* <signer>" instead.
|
|
36
|
+
* See `SLinkClaimType` / `SLinkVerificationMethod` for the full contract.
|
|
37
|
+
*
|
|
38
|
+
* For "dns_txt" verification, the owner publishes `dnsRecordValue` at
|
|
39
|
+
* `dnsRecordName`. For "page_content", the owner embeds `vCode` anywhere in
|
|
40
|
+
* the public page (description, bio, comment, HTML body) and a scraper
|
|
41
|
+
* verifies its presence. Once found, the MajikSignature certifies the
|
|
42
|
+
* association between the signer's identity and the URL.
|
|
28
43
|
*
|
|
29
44
|
* Canonical URL format (what is signed):
|
|
30
45
|
* "majik-slink-v1:<subdomain>::<sld>::<tld>::<path>"
|
|
@@ -52,8 +67,10 @@ export class MajikSLink {
|
|
|
52
67
|
_source;
|
|
53
68
|
_status;
|
|
54
69
|
_verified_at;
|
|
70
|
+
_claim_type;
|
|
71
|
+
_verification_method;
|
|
55
72
|
_signature;
|
|
56
|
-
|
|
73
|
+
_resolvedSignature = null;
|
|
57
74
|
_timestamp;
|
|
58
75
|
// ── Private constructor — use MajikSLink.create() ────────────────────────
|
|
59
76
|
constructor(data) {
|
|
@@ -71,6 +88,12 @@ export class MajikSLink {
|
|
|
71
88
|
this._hash = data.hash;
|
|
72
89
|
this._v_code = data.v_code;
|
|
73
90
|
this._source = data.source;
|
|
91
|
+
// `??` — not `||` — so an explicit `null` (a deliberate "no verification
|
|
92
|
+
// possible" for claim_type "reference") is preserved rather than being
|
|
93
|
+
// silently overwritten by the default.
|
|
94
|
+
this._claim_type = data.claim_type ?? "ownership";
|
|
95
|
+
this._verification_method =
|
|
96
|
+
data.verification_method ?? defaultVerificationMethod(this._claim_type);
|
|
74
97
|
this._status = data.status;
|
|
75
98
|
this._verified_at = data.verified_at;
|
|
76
99
|
this._signature = data.signature;
|
|
@@ -135,22 +158,53 @@ export class MajikSLink {
|
|
|
135
158
|
return this._verified_at;
|
|
136
159
|
}
|
|
137
160
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
161
|
+
* The resolved MajikSignature instance, if `resolveSignature()` has been
|
|
162
|
+
* called. Null until then — MajikSLink stores only the compact envelope
|
|
163
|
+
* (no embedded public keys), so there is nothing to eagerly resolve.
|
|
140
164
|
*/
|
|
141
165
|
get signature() {
|
|
142
|
-
|
|
143
|
-
this._signatureInstance = MajikSignature.fromJSON(this._signature);
|
|
144
|
-
}
|
|
145
|
-
return this._signatureInstance;
|
|
166
|
+
return this._resolvedSignature;
|
|
146
167
|
}
|
|
147
168
|
/**
|
|
148
|
-
* Get the raw signature JSON.
|
|
149
|
-
*
|
|
169
|
+
* Get the raw compact signature JSON as stored/persisted.
|
|
170
|
+
* No public keys are embedded — resolve them externally (e.g. by
|
|
171
|
+
* `signerId` / `muid` from your key registry) before verifying.
|
|
150
172
|
*/
|
|
151
173
|
get signatureJSON() {
|
|
152
174
|
return this._signature;
|
|
153
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Resolve the compact signature into a full MajikSignature instance, using
|
|
178
|
+
* externally-supplied public keys (looked up by `this.muid` or
|
|
179
|
+
* `this.signatureJSON.signerId` from your key registry).
|
|
180
|
+
*
|
|
181
|
+
* There is nothing to fall back to by design — MajikSLink never stores
|
|
182
|
+
* public keys inline, so `publicKeys` is mandatory. The result is cached;
|
|
183
|
+
* subsequent calls with the same (correct) keys are free.
|
|
184
|
+
*
|
|
185
|
+
* @throws {MajikSLinkValidationError} if `publicKeys.signerId` does not
|
|
186
|
+
* match the signerId recorded on this SLink's signature.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* const keys = await resolvePublicKeysForMuid(slink.muid);
|
|
190
|
+
* const sig = slink.resolveSignature(keys);
|
|
191
|
+
* console.log(sig.hasTSA);
|
|
192
|
+
*/
|
|
193
|
+
resolveSignature(publicKeys) {
|
|
194
|
+
if (!this._resolvedSignature) {
|
|
195
|
+
if (publicKeys.signerId !== this._signature.signerId) {
|
|
196
|
+
throw new MajikSLinkValidationError(`publicKeys are for signer "${publicKeys.signerId}" but this SLink was signed by "${this._signature.signerId}".`);
|
|
197
|
+
}
|
|
198
|
+
this._resolvedSignature = MajikSignature.fromCompact(this._signature, publicKeys);
|
|
199
|
+
}
|
|
200
|
+
return this._resolvedSignature;
|
|
201
|
+
}
|
|
202
|
+
get claimType() {
|
|
203
|
+
return this._claim_type;
|
|
204
|
+
}
|
|
205
|
+
get verificationMethod() {
|
|
206
|
+
return this._verification_method;
|
|
207
|
+
}
|
|
154
208
|
get timestamp() {
|
|
155
209
|
return this._timestamp;
|
|
156
210
|
}
|
|
@@ -167,6 +221,27 @@ export class MajikSLink {
|
|
|
167
221
|
}
|
|
168
222
|
return chunks;
|
|
169
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* The DNS TXT record name to publish for `"dns_txt"` verification.
|
|
226
|
+
* Only meaningful when `verificationMethod === "dns_txt"` — for
|
|
227
|
+
* `"page_content"` or `"reference"` SLinks this value is simply
|
|
228
|
+
* irrelevant (not an error), so check `verificationMethod` before using it.
|
|
229
|
+
*
|
|
230
|
+
* @example "_majik-challenge.example.com"
|
|
231
|
+
*/
|
|
232
|
+
get dnsRecordName() {
|
|
233
|
+
return `_majik-challenge.${this._domain}`;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The DNS TXT record value to publish for `"dns_txt"` verification.
|
|
237
|
+
* See `dnsRecordName` — only meaningful when
|
|
238
|
+
* `verificationMethod === "dns_txt"`.
|
|
239
|
+
*
|
|
240
|
+
* @example "majik-slink-verify=majik-slink:a1b2c3d4..."
|
|
241
|
+
*/
|
|
242
|
+
get dnsRecordValue() {
|
|
243
|
+
return `majik-slink-verify=${this._v_code}`;
|
|
244
|
+
}
|
|
170
245
|
// ── Static: create ────────────────────────────────────────────────────────
|
|
171
246
|
/**
|
|
172
247
|
* Create and sign a new MajikSLink.
|
|
@@ -202,16 +277,14 @@ export class MajikSLink {
|
|
|
202
277
|
if (!urlInfo) {
|
|
203
278
|
throw new MajikSLinkValidationError(`Could not parse URL into a valid domain: "${rawUrl}". Ensure it is a public https:// URL.`);
|
|
204
279
|
}
|
|
205
|
-
|
|
280
|
+
const ts = options?.timestamp ?? new Date();
|
|
206
281
|
const hash = await sha256Hex(urlInfo.canonical);
|
|
207
282
|
const v_code = `${CODE_PREFIX}${hash.slice(0, CODE_HEX_LENGTH)}`;
|
|
208
|
-
// ── Sign with MajikSignature ─────────────────────────────────────────
|
|
209
283
|
let signature;
|
|
210
284
|
try {
|
|
211
|
-
signature = await MajikSignature.sign(urlInfo.canonical,
|
|
212
|
-
key, {
|
|
285
|
+
signature = await MajikSignature.sign(urlInfo.canonical, key, {
|
|
213
286
|
contentType: "majik-slink/url",
|
|
214
|
-
timestamp:
|
|
287
|
+
timestamp: ts.toISOString(),
|
|
215
288
|
});
|
|
216
289
|
}
|
|
217
290
|
catch (err) {
|
|
@@ -221,7 +294,7 @@ export class MajikSLink {
|
|
|
221
294
|
version: SLINK_VERSION,
|
|
222
295
|
id: options?.id ?? generateId(),
|
|
223
296
|
user_id: userId,
|
|
224
|
-
muid
|
|
297
|
+
muid,
|
|
225
298
|
domain: urlInfo.domain,
|
|
226
299
|
sld: urlInfo.sld,
|
|
227
300
|
tld: urlInfo.tld,
|
|
@@ -232,10 +305,12 @@ export class MajikSLink {
|
|
|
232
305
|
hash,
|
|
233
306
|
v_code,
|
|
234
307
|
source: detectSource(urlInfo.sld),
|
|
308
|
+
claim_type: options?.claimType ?? "ownership",
|
|
309
|
+
verification_method: options?.verificationMethod ?? null,
|
|
235
310
|
status: options?.status ?? "unverified",
|
|
236
311
|
verified_at: null,
|
|
237
|
-
signature: signature.
|
|
238
|
-
timestamp:
|
|
312
|
+
signature: signature.toCompact(),
|
|
313
|
+
timestamp: ts,
|
|
239
314
|
});
|
|
240
315
|
}
|
|
241
316
|
// ── Static: generateChallenge ─────────────────────────────────────────────
|
|
@@ -268,65 +343,63 @@ export class MajikSLink {
|
|
|
268
343
|
}
|
|
269
344
|
// ── Static: verifySignature ───────────────────────────────────────────────
|
|
270
345
|
/**
|
|
271
|
-
* Verify the
|
|
346
|
+
* Verify the stored MajikSignature of a persisted MajikSLink against
|
|
347
|
+
* externally-supplied public keys.
|
|
272
348
|
*
|
|
273
|
-
* This does NOT scrape the target page — it only
|
|
274
|
-
* that the signature covers the canonical URL
|
|
275
|
-
*
|
|
349
|
+
* This does NOT scrape the target page or check DNS — it only
|
|
350
|
+
* cryptographically checks that the signature covers the canonical URL
|
|
351
|
+
* and was issued by the holder of `publicKeys`. No public keys are
|
|
352
|
+
* embedded in the stored SLink; you must resolve them yourself (e.g. by
|
|
353
|
+
* `signerId` / `muid` from your key registry) before calling this.
|
|
276
354
|
*
|
|
277
|
-
* Use in conjunction with your
|
|
278
|
-
* 1.
|
|
355
|
+
* Use in conjunction with your DNS/scrape check to fully verify a claim:
|
|
356
|
+
* 1. Look up (dns_txt) or scrape (page_content) per `verificationMethod`
|
|
357
|
+
* and confirm `v_code` is present.
|
|
279
358
|
* 2. Call `verifySignature()` to confirm the cryptographic provenance.
|
|
280
359
|
*
|
|
281
360
|
* @example
|
|
282
|
-
* const
|
|
283
|
-
*
|
|
361
|
+
* const keys = await resolvePublicKeysForMuid(slink.muid);
|
|
362
|
+
* const result = MajikSLink.verifySignature(slink, keys);
|
|
363
|
+
* if (!result.valid) console.warn("Signature invalid:", result.reason);
|
|
284
364
|
*/
|
|
285
365
|
static verifySignature(slink, publicKeys) {
|
|
286
366
|
const json = slink instanceof MajikSLink ? slink.toJSON() : slink;
|
|
287
|
-
// Reconstruct the canonical string from stored parts
|
|
288
|
-
// We need to extract sld and tld from the domain field
|
|
289
367
|
const parts = json.domain.split(".");
|
|
290
368
|
const tld = parts[parts.length - 1] ?? "";
|
|
291
369
|
const sld = parts.slice(0, -1).join(".") || json.domain;
|
|
292
370
|
const canonical = buildCanonical(json.subdomain, sld, tld, json.path);
|
|
293
|
-
return MajikSignature.
|
|
371
|
+
return MajikSignature.verifyCompact(canonical, json.signature, publicKeys);
|
|
294
372
|
}
|
|
295
373
|
// ── Instance: verify ──────────────────────────────────────────────────────
|
|
296
374
|
/**
|
|
297
|
-
* Verify this SLink's
|
|
375
|
+
* Verify this SLink's stored signature against externally-supplied
|
|
376
|
+
* public keys.
|
|
298
377
|
*
|
|
299
378
|
* This validates that:
|
|
300
379
|
* 1. The signature cryptographically matches the canonical URL
|
|
301
380
|
* 2. The signature was created by the holder of the private key
|
|
381
|
+
* corresponding to `publicKeys`
|
|
302
382
|
*
|
|
303
|
-
* Note: This does NOT scrape the page. Use with
|
|
304
|
-
* a successful
|
|
383
|
+
* Note: This does NOT scrape the page or check DNS. Use with
|
|
384
|
+
* `markVerified()` after a successful DNS lookup / scrape (per
|
|
385
|
+
* `verificationMethod`) to complete the verification flow.
|
|
305
386
|
*
|
|
306
|
-
* @param publicKeys The signer's public keys (classic Ed25519 + ML-DSA-87)
|
|
387
|
+
* @param publicKeys The signer's public keys (classic Ed25519 + ML-DSA-87),
|
|
388
|
+
* resolved externally — e.g. by `this.muid`.
|
|
307
389
|
* @returns Verification result with valid/invalid status and optional reason
|
|
308
390
|
*
|
|
309
391
|
* @example
|
|
310
|
-
* // After
|
|
311
|
-
* const
|
|
392
|
+
* // After confirming v_code via DNS or page scrape:
|
|
393
|
+
* const keys = await resolvePublicKeysForMuid(slink.muid);
|
|
394
|
+
* const result = slink.verify(keys);
|
|
312
395
|
* if (result.valid) {
|
|
313
396
|
* slink.markVerified();
|
|
314
397
|
* await db.save(slink);
|
|
315
398
|
* }
|
|
316
399
|
*/
|
|
317
400
|
verify(publicKeys) {
|
|
318
|
-
const signerKeys = {
|
|
319
|
-
edPublicKey: publicKeys?.edPublicKey ||
|
|
320
|
-
base64ToBytes(this.signature.signerEdPublicKey),
|
|
321
|
-
mlDsaPublicKey: publicKeys?.mlDsaPublicKey ||
|
|
322
|
-
base64ToBytes(this.signature.signerMlDsaPublicKey),
|
|
323
|
-
signerId: publicKeys?.signerId || this.signature.signerId,
|
|
324
|
-
};
|
|
325
|
-
// Rebuild canonical string from stored parts
|
|
326
401
|
const canonical = buildCanonical(this._subdomain, this._sld, this._tld, this._path);
|
|
327
|
-
this.
|
|
328
|
-
// Verify the signature against the canonical URL
|
|
329
|
-
return MajikSignature.verify(canonical, this._signature, signerKeys);
|
|
402
|
+
return MajikSignature.verifyCompact(canonical, this._signature, publicKeys);
|
|
330
403
|
}
|
|
331
404
|
// ── Status mutation ───────────────────────────────────────────────────────
|
|
332
405
|
/**
|
|
@@ -403,6 +476,8 @@ export class MajikSLink {
|
|
|
403
476
|
hash: this._hash,
|
|
404
477
|
v_code: this._v_code,
|
|
405
478
|
source: this._source,
|
|
479
|
+
claim_type: this._claim_type,
|
|
480
|
+
verification_method: this._verification_method,
|
|
406
481
|
status: this._status,
|
|
407
482
|
timestamp: this._timestamp.toISOString(),
|
|
408
483
|
verified_at: this._verified_at?.toISOString() ?? null,
|
|
@@ -411,7 +486,8 @@ export class MajikSLink {
|
|
|
411
486
|
}
|
|
412
487
|
/**
|
|
413
488
|
* Rehydrate a MajikSLink from a plain JSON object (e.g. from a database row).
|
|
414
|
-
* Does NOT re-verify the signature — call `verify()` explicitly
|
|
489
|
+
* Does NOT re-verify the signature — call `verify()` explicitly with
|
|
490
|
+
* externally-resolved public keys.
|
|
415
491
|
*
|
|
416
492
|
* @throws {MajikSLinkSerializationError} if any required field is missing or malformed.
|
|
417
493
|
*/
|
|
@@ -427,10 +503,23 @@ export class MajikSLink {
|
|
|
427
503
|
assertNonEmptyString(json?.hash, "hash");
|
|
428
504
|
assertNonEmptyString(json?.v_code, "v_code");
|
|
429
505
|
assertNonEmptyString(json?.source, "source");
|
|
506
|
+
assertNonEmptyString(json?.claim_type, "claim_type");
|
|
430
507
|
assertNonEmptyString(json?.status, "status");
|
|
431
508
|
assertNonEmptyString(json?.timestamp, "timestamp");
|
|
509
|
+
if (json?.claim_type !== "ownership" &&
|
|
510
|
+
json?.claim_type !== "attribution" &&
|
|
511
|
+
json?.claim_type !== "reference") {
|
|
512
|
+
throw new MajikSLinkSerializationError(`"claim_type" must be "ownership", "attribution", or "reference". Got: ${String(json?.claim_type)}`);
|
|
513
|
+
}
|
|
514
|
+
// verification_method is nullable by design — check it's a valid
|
|
515
|
+
// value rather than asserting non-empty.
|
|
516
|
+
if (json?.verification_method !== null &&
|
|
517
|
+
json?.verification_method !== "dns_txt" &&
|
|
518
|
+
json?.verification_method !== "page_content") {
|
|
519
|
+
throw new MajikSLinkSerializationError(`"verification_method" must be "dns_txt", "page_content", or null. Got: ${String(json?.verification_method)}`);
|
|
520
|
+
}
|
|
432
521
|
if (!json?.signature || typeof json.signature !== "object") {
|
|
433
|
-
throw new MajikSLinkSerializationError('"signature" must be a
|
|
522
|
+
throw new MajikSLinkSerializationError('"signature" must be a MajikSignatureCompactJSON object.');
|
|
434
523
|
}
|
|
435
524
|
// Derive sld / tld from stored domain ("youtube.com" → "youtube", "com")
|
|
436
525
|
// Note: This is a simplified approach. For production use with multi-part
|
|
@@ -459,6 +548,8 @@ export class MajikSLink {
|
|
|
459
548
|
hash: json.hash,
|
|
460
549
|
v_code: json.v_code,
|
|
461
550
|
source: json.source,
|
|
551
|
+
claim_type: json.claim_type,
|
|
552
|
+
verification_method: json.verification_method,
|
|
462
553
|
status: json.status,
|
|
463
554
|
verified_at: json.verified_at ? new Date(json.verified_at) : null,
|
|
464
555
|
signature: json.signature,
|
|
@@ -519,6 +610,7 @@ export class MajikSLink {
|
|
|
519
610
|
` user: ${this._user_id}`,
|
|
520
611
|
` url: ${this._clean_url}`,
|
|
521
612
|
` v_code: ${this._v_code}`,
|
|
613
|
+
` claim: ${this._claim_type} (${this._verification_method ?? "no verification possible"})`,
|
|
522
614
|
` status: ${this._status}`,
|
|
523
615
|
` source: ${this._source}`,
|
|
524
616
|
` signed: ${this._timestamp.toISOString()}`,
|
|
@@ -526,3 +618,7 @@ export class MajikSLink {
|
|
|
526
618
|
].join("\n");
|
|
527
619
|
}
|
|
528
620
|
}
|
|
621
|
+
// Freeze static methods
|
|
622
|
+
Object.freeze(MajikSLink);
|
|
623
|
+
// Freeze instance methods
|
|
624
|
+
Object.freeze(MajikSLink.prototype);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@majikah/majik-slink",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "Majik SLink is a specialized TypeScript library designed for URL ownership verification and cryptographic identity linking. It provides a robust mechanism to prove that a specific digital identity (a MajikKey) controls a publicly accessible web resource (such as a YouTube channel, social media profile, or personal blog).",
|
|
5
|
-
"version": "0.0
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -41,17 +41,22 @@
|
|
|
41
41
|
"url": "https://github.com/Majikah/majik-slink/issues"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
45
44
|
"build": "tsc",
|
|
46
|
-
"
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"package": "npm run build && npm version patch && git push && git push --tags",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest",
|
|
50
|
+
"update": "npm i @majikah/majik-key@latest @majikah/majik-signature@latest"
|
|
47
51
|
},
|
|
48
52
|
"dependencies": {
|
|
49
|
-
"@majikah/majik-key": "^0.
|
|
50
|
-
"@majikah/majik-signature": "^0.0
|
|
53
|
+
"@majikah/majik-key": "^0.4.0",
|
|
54
|
+
"@majikah/majik-signature": "^0.3.0",
|
|
51
55
|
"@types/psl": "^1.1.3",
|
|
52
56
|
"psl": "^1.15.0"
|
|
53
57
|
},
|
|
54
58
|
"devDependencies": {
|
|
55
|
-
"@types/node": "^
|
|
59
|
+
"@types/node": "^26.2.0",
|
|
60
|
+
"vitest": "^4.1.10"
|
|
56
61
|
}
|
|
57
62
|
}
|