@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 CHANGED
@@ -1,67 +1,67 @@
1
- Copyright (c) 2025 Josef Elijah Delos Santos Fabian
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
- [![Developed by Zelijah](https://img.shields.io/badge/Developed%20by-Zelijah-red?logo=github&logoColor=white)](https://thezelijah.world) ![GitHub Sponsors](https://img.shields.io/github/sponsors/jedlsf?style=plastic&label=Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fjedlsf)
3
+ [![Developed by Zelijah](https://img.shields.io/badge/Developed%20by-Zelijah-red?logo=github&logoColor=white)](https://www.thezelijah.world) ![GitHub Sponsors](https://img.shields.io/github/sponsors/jedlsf?style=plastic&label=Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fjedlsf)
4
+ ![npm](https://img.shields.io/npm/v/@majikah/majik-slink) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-slink) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
4
5
 
5
- **MajikSLink** 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).
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** (Dilithium) hybrid cryptography, MajikSLink ensures that your identity-to-URL associations are tamper-proof and future-proof.
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
- Identities can be created and managed via the web application at **[https://id.majikah.solutions](https://id.majikah.solutions)** using your own Majik Keys.
14
+ ## Key Features
12
15
 
13
- ![npm](https://img.shields.io/npm/v/@majikah/majik-slink) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-slink) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
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
- ## Key Features:
19
- 1. **Deterministic URL Normalization:** Automatically converts URLs into a canonical format (stripping query params and fragments) so that the same resource always generates the same verification code.
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
- 2. **Challenge-Response Verification:** Generates short, embeddable verification codes (v_code) that users can place in their bios or page metadata for automated scrapers to detect.
40
+ ```typescript
41
+ import { MajikSLink } from "@majikah/majik-slink";
22
42
 
23
- 3. **Hybrid Signing:** Leverages [@majikah/majik-signature](https://www.npmjs.com/package/@majikah/majik-signature) to sign canonical URL data with both classical and post-quantum algorithms.
43
+ const preview = await MajikSLink.generateChallenge("https://github.com/majikah/repo?utm_source=test#readme");
24
44
 
25
- 4. **Proof of Ownership:** Enables a trustless workflow where a crawler can verify the presence of a code, and the library verifies the cryptographic signature against the owner's public keys.
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
- 5. **Seamless Serialization:** Includes built-in support for JSON rehydration and Base64 serialization for easy storage in databases or transmission via QR codes.
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
- ## Related Projects
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
- ### [Majik Signature](https://www.npmjs.com/package/@majikah/majik-signature)
34
- Hybrid post-quantum content signing the signing engine used by `signContent()` and `signFile()`.
66
+ const slink = await MajikSLink.create(targetUrl, key, userId, muid, {
67
+ claimType: "attribution" // Automatically defaults verificationMethod to "page_content"
68
+ });
35
69
 
36
- [Read Docs](https://majikah.solutions/products/majik-signature/docs) · [Microsoft Store](https://apps.microsoft.com/detail/9pl9g3xzvd1x)
70
+ console.log("Status:", slink.status); // "unverified"
71
+ console.log("Source Platform:", slink.source); // "youtube"
72
+ ```
37
73
 
38
- [![Majik Signature Microsoft App Store](https://get.microsoft.com/images/en-us%20light.svg)](https://apps.microsoft.com/detail/9pl9g3xzvd1x)
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
- ### [Majik Message](https://message.majikah.solutions)
43
- Secure messaging platform using Majik Keys and Majik Signatures for identity-bound communication.
94
+ ```typescript
95
+ import { MajikSignature } from "@majikah/majik-signature";
44
96
 
45
- [Read Docs](https://majikah.solutions/products/majik-message/docs) · [Microsoft Store](https://apps.microsoft.com/detail/9pmjgvzzjspn)
97
+ // 1. Fetch public keys from your trusted registry using the SLink's MUID
98
+ const publicKeys = await fetchPublicKeysForMuid(slink.muid);
46
99
 
47
- [![Majik Message Microsoft App Store](https://get.microsoft.com/images/en-us%20light.svg)](https://apps.microsoft.com/detail/9pmjgvzzjspn)
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
- ### [Majik Key](https://www.npmjs.com/package/@majikah/majik-key)
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
- [Read More Information](https://majikah.solutions/sdk/majik-key)
115
+ ### 5. Utilities & Comparisons
116
+ Easily compare URLs to see if they resolve to the exact same signed resource:
54
117
 
55
- ### [Majik Envelope](https://www.npmjs.com/package/@majikah/majik-envelope)
56
- Post-quantum group encryption used to encrypt and share private personal info.
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
- [Read More Information](https://majikah.solutions/sdk/majik-envelope)
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
- ### [Majik Universal ID](https://id.majikah.solutions)
62
- A cryptographically anchored identity layer for the Majikah ecosystem.
130
+ ```typescript
131
+ // Export to JSON for Database storage
132
+ const jsonPayload = slink.toJSON();
63
133
 
64
- [Read More Information](https://majikah.solutions/sdk/majik-universal-id)
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
- ## Contributing
144
+ ## Related Ecosystem Projects
70
145
 
71
- If you want to contribute or help extend support, reach out via email. All contributions are welcome!
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
- Made with 💙 by [@thezelijah](https://github.com/jedlsf)
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/jedlsf](https://github.com/jedlsf)
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@thezelijah.world](mailto:business@thezelijah.world)
172
+ - **Business Email**: [business@majikah.solutions](mailto:business@majikah.solutions)
94
173
  - **Official Website**: [https://www.thezelijah.world](https://www.thezelijah.world)
95
- - **ID Web App**: [https://id.majikah.solutions](https://id.majikah.solutions)
174
+ - **Majikah Ecosystem**: [https://majikah.solutions](https://majikah.solutions)
@@ -1,26 +1,23 @@
1
- import { MajikSignatureJSON } from "@majikah/majik-signature";
2
- /** Source platform of the signed URL */
3
- export type SLinkSource = "youtube" | "x" | "instagram" | "tiktok" | "github" | "website" | "other";
4
- /** Structured URL parts extracted during normalisation */
5
- export interface UrlInfo {
6
- /** Full registered domain e.g. "youtube.com" */
7
- domain: string;
8
- /** Second-level domain only e.g. "youtube" */
9
- sld: string;
10
- /** Top-level domain e.g. "com" */
11
- tld: string;
12
- /** Subdomain or null e.g. "www" | null */
13
- subdomain: string | null;
14
- /** Normalised pathname e.g. "/watch/dQw4w9WgXcQ" */
15
- path: string;
16
- /** Reconstructed clean URL (no params, no fragment) */
17
- cleanUrl: string;
18
- /** The string that is actually hashed + signed */
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: string;
45
+ timestamp: ISODateString;
47
46
  /** ISO timestamp of the last verification attempt, or null */
48
- verified_at: string | null;
49
- /** The MajikSignature envelope covering the canonical URL */
50
- signature: MajikSignatureJSON;
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
+ }
@@ -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;
@@ -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";
@@ -2,13 +2,13 @@
2
2
  * majik-slink.ts
3
3
  */
4
4
  import type { MajikKey } from "@majikah/majik-key";
5
- import { MajikSignature, type MajikSignatureJSON } from "@majikah/majik-signature";
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 signing and verification.
11
- * A MajikSLink proves that the holder of a MajikKey owns (or controls) a
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 owner's MajikKey via
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 owner embeds the verification code (`v_code`) anywhere in the public
23
- * page (description, bio, comment, HTML body) and a scraper verifies its
24
- * presence. Once found, the MajikSignature certifies the association between
25
- * the signer's identity and the URL.
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 _signatureInstance;
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
- * Get the signature as a MajikSignature instance.
83
- * The instance is cached to avoid re-parsing on every access.
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 signature(): MajikSignature;
109
+ get signatureJSON(): MajikSignatureCompactJSON;
86
110
  /**
87
- * Get the raw signature JSON.
88
- * Useful when you need the serializable form without parsing.
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
- get signatureJSON(): MajikSignatureJSON;
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 embedded MajikSignature of a persisted MajikSLink.
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 cryptographically checks
141
- * that the signature covers the canonical URL and was issued by the stated
142
- * signer.
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 scraper to fully verify ownership:
145
- * 1. Scrape the page and confirm `v_code` is present.
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 result = MajikSLink.verifySignature(slink, signerPublicKeys);
150
- * if (!result.valid) console.warn("Signature tampered:", result.reason);
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: Parameters<typeof MajikSignature.verify>[2]): ReturnType<typeof MajikSignature.verify>;
215
+ static verifySignature(slink: MajikSLink | MajikSLinkJSON, publicKeys: MajikSignerPublicKeys): VerificationResult;
153
216
  /**
154
- * Verify this SLink's embedded signature against the provided public keys.
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 `markVerified()` after
161
- * a successful scrape to complete the verification flow.
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 scraping and finding v_code on the page:
168
- * const result = slink.verify(user.publicKeys);
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?: Parameters<typeof MajikSignature.verify>[2]): ReturnType<typeof MajikSignature.verify>;
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
  */
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * majik-slink.ts
3
3
  */
4
- import { base64ToBytes, MajikSignature, } from "@majikah/majik-signature";
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 signing and verification.
13
- * A MajikSLink proves that the holder of a MajikKey owns (or controls) a
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 owner's MajikKey via
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 owner embeds the verification code (`v_code`) anywhere in the public
25
- * page (description, bio, comment, HTML body) and a scraper verifies its
26
- * presence. Once found, the MajikSignature certifies the association between
27
- * the signer's identity and the URL.
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
- _signatureInstance = null; // Cached instance
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
- * Get the signature as a MajikSignature instance.
139
- * The instance is cached to avoid re-parsing on every access.
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
- if (!this._signatureInstance) {
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
- * Useful when you need the serializable form without parsing.
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
- // ── Hash the canonical string ────────────────────────────────────────
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, // what we're signing
212
- key, {
285
+ signature = await MajikSignature.sign(urlInfo.canonical, key, {
213
286
  contentType: "majik-slink/url",
214
- timestamp: (options?.timestamp ?? new Date()).toISOString(),
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: 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.toJSON(),
238
- timestamp: options?.timestamp ?? new Date(),
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 embedded MajikSignature of a persisted MajikSLink.
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 cryptographically checks
274
- * that the signature covers the canonical URL and was issued by the stated
275
- * signer.
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 scraper to fully verify ownership:
278
- * 1. Scrape the page and confirm `v_code` is present.
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 result = MajikSLink.verifySignature(slink, signerPublicKeys);
283
- * if (!result.valid) console.warn("Signature tampered:", result.reason);
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.verify(canonical, json.signature, publicKeys);
371
+ return MajikSignature.verifyCompact(canonical, json.signature, publicKeys);
294
372
  }
295
373
  // ── Instance: verify ──────────────────────────────────────────────────────
296
374
  /**
297
- * Verify this SLink's embedded signature against the provided public keys.
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 `markVerified()` after
304
- * a successful scrape to complete the verification flow.
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 scraping and finding v_code on the page:
311
- * const result = slink.verify(user.publicKeys);
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.signature.validate();
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 MajikSignatureJSON object.');
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.2",
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
- "prepublishOnly": "npm run build"
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.2.3",
50
- "@majikah/majik-signature": "^0.0.15",
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": "^25.5.0"
59
+ "@types/node": "^26.2.0",
60
+ "vitest": "^4.1.10"
56
61
  }
57
62
  }