├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .husky ├── pre-commit └── pre-push ├── .npmignore ├── .npmrc ├── LICENSE ├── babel.config.json ├── jest.config.ts ├── package-lock.json ├── package.json ├── readme.MD ├── src ├── config │ └── index.ts ├── index.ts ├── methods │ ├── accessControl │ │ ├── index.ts │ │ ├── test │ │ │ └── accessControl.spec.ts │ │ └── validator.ts │ ├── generate │ │ ├── index.ts │ │ └── test │ │ │ └── generate.test.ts │ ├── getAccessCondition │ │ ├── index.ts │ │ └── test │ │ │ └── getAccessCondition.test.ts │ ├── getAuthMessage │ │ ├── index.ts │ │ └── test │ │ │ └── getAuthMessage.test.ts │ ├── getJWT │ │ ├── index.ts │ │ └── test │ │ │ └── getJWT.spec.ts │ ├── index.ts │ ├── recoverKey │ │ ├── index.ts │ │ └── test │ │ │ └── recoverKey.test.ts │ ├── recoverShards │ │ ├── index.ts │ │ └── test │ │ │ └── recoverShards.test.ts │ ├── revokeAccess │ │ ├── index.ts │ │ └── test │ │ │ └── revoke.test.ts │ ├── saveShards │ │ ├── index.ts │ │ └── test │ │ │ └── saveShards.test.ts │ ├── shardKey │ │ ├── index.ts │ │ └── test │ │ │ └── shardKey.test.ts │ ├── shareToAddress │ │ ├── index.ts │ │ └── test │ │ │ └── sharedToAddress.spec.ts │ └── transferOwnership │ │ ├── index.ts │ │ └── test │ │ └── transferOwnership.test.ts ├── types.ts └── util │ └── index.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | middlewares/authenticator.js 2 | **/*.test.js 3 | **/*.spec.js 4 | *.test.js 5 | *.spec.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": [], 4 | "parser": "@typescript-eslint/parser", 5 | "overrides": [ 6 | { 7 | "files": [ 8 | "*.ts" 9 | ], 10 | "parserOptions": { 11 | "project": [ 12 | "tsconfig.json" 13 | ], 14 | "createDefaultProgram": true 15 | }, 16 | "extends": [ 17 | "eslint:recommended", 18 | "plugin:@typescript-eslint/recommended", 19 | "plugin:prettier/recommended" 20 | ], 21 | "rules": { 22 | "max-len": "off", 23 | "no-underscore-dangle": "off", 24 | "arrow-body-style": "off", 25 | "@typescript-eslint/no-explicit-any": "off", 26 | "@typescript-eslint/no-unsafe-assignment": "off", 27 | "@typescript-eslint/no-unsafe-member-access": "off", 28 | "@typescript-eslint/no-unsafe-call": "off", 29 | "@typescript-eslint/unbound-method": "off", 30 | "@typescript-eslint/no-floating-promises": "off", 31 | "@typescript-eslint/explicit-module-boundary-types": "off", 32 | "@typescript-eslint/no-unused-vars": "off", 33 | "@typescript-eslint/naming-convention": "off", 34 | "@typescript-eslint/no-unsafe-return": "off", 35 | "@typescript-eslint/no-empty-function": "off", 36 | "@typescript-eslint/no-inferrable-types": "off", 37 | "@typescript-eslint/restrict-template-expressions": "warn", 38 | "jsdoc/newline-after-description": "off" 39 | } 40 | } 41 | ], 42 | "plugins": [ 43 | "@typescript-eslint" 44 | ] 45 | } -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 18 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm ci 20 | - run: npm test 21 | - run: npm run build 22 | - run: npm publish --access=public 23 | env: 24 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTOMATION}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.tgz 4 | temp 5 | yarn-error.log 6 | .vscode/ 7 | *.txt 8 | dist/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | # npm test 6 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run build 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | *.tgz 4 | temp 5 | yarn-error.log 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @lighthouse-web3:registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-typescript", "@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | const coverageToNumber = 50; // [0..100] 2 | 3 | /* 4 | * For a detailed explanation regarding each configuration property and type check, visit: 5 | * https://jestjs.io/docs/configuration 6 | */ 7 | 8 | export default { 9 | testTimeout: 600 * 1000, 10 | setTimeout: 600 * 1000, 11 | verbose: true, 12 | rootDir: "./", 13 | clearMocks: true, // clear mocks before every test 14 | resetMocks: false, // reset mock state before every test 15 | testMatch: [ 16 | // '/src/**/*.spec.ts', // Commenting cache test for github actions 17 | "/src/**/*.test.ts", 18 | "/src/**/*.test.js", 19 | ], // match only tests inside /tests folder 20 | testPathIgnorePatterns: ["/node_modules/"], // exclude unnecessary folders 21 | 22 | // following lines are about coverage 23 | collectCoverage: true, 24 | collectCoverageFrom: ["/src/**/*.ts", "/src/**/*.js"], 25 | coverageDirectory: "/coverage", 26 | coverageReporters: ["lcov"], 27 | coverageThreshold: { 28 | global: { 29 | branches: coverageToNumber, 30 | functions: coverageToNumber, 31 | lines: coverageToNumber, 32 | statements: coverageToNumber, 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lighthouse-web3/kavach", 3 | "version": "0.2.1", 4 | "description": "Encryption SDK: Build your trustless, decentralized and fault resistance Application using distributed key shades with threshold cryptography", 5 | "author": "xlassix", 6 | "main": "./dist/methods/index.js", 7 | "types": "./dist/methods/index.d.ts", 8 | "license": "MIT", 9 | "files": [ 10 | "dist/**/*" 11 | ], 12 | "dependencies": { 13 | "bls-eth-wasm": "^1.1.1", 14 | "joi": "^17.7.0" 15 | }, 16 | "engines": { 17 | "node": ">=18.0.0" 18 | }, 19 | "lint-staged": { 20 | "**/*.ts": "eslint --fix", 21 | "src/**/*": [ 22 | "prettier --single-quote --write" 23 | ] 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "prettier" 28 | ] 29 | }, 30 | "scripts": { 31 | "test": "jest --runInBand", 32 | "coverage": "jest --runInBand --coverage", 33 | "build": "npx tsc", 34 | "lint": "eslint . --ext .ts", 35 | "prepare": "husky install" 36 | }, 37 | "devDependencies": { 38 | "@babel/cli": "^7.20.7", 39 | "@babel/core": "^7.20.12", 40 | "@babel/preset-env": "^7.20.2", 41 | "@babel/preset-typescript": "^7.18.6", 42 | "@types/jest": "^29.5.8", 43 | "@types/node": "^20.9.1", 44 | "@typescript-eslint/eslint-plugin": "^5.6.0", 45 | "@typescript-eslint/parser": "^5.6.0", 46 | "babel-jest": "^29.4.1", 47 | "ethers": "^6.3.1", 48 | "eslint": "8.25.0", 49 | "eslint-config-airbnb": "^19.0.2", 50 | "eslint-config-airbnb-typescript": "17.0.0", 51 | "eslint-config-next": "12.3.1", 52 | "eslint-config-prettier": "^8.3.0", 53 | "eslint-config-react": "1.1.7", 54 | "eslint-plugin-import": "^2.25.3", 55 | "eslint-plugin-jsx-a11y": "^6.5.1", 56 | "eslint-plugin-prettier": "^4.0.0", 57 | "eslint-plugin-react": "^7.27.1", 58 | "eslint-plugin-react-hooks": "^4.3.0", 59 | "eslint-plugin-testing-library": "5.5.1", 60 | "git-format-staged": "^3.0.0", 61 | "husky": "^8.0.3", 62 | "jest": "^29.3.1", 63 | "jest-environment-jsdom": "^29.7.0", 64 | "lint-staged": "^14.0.1", 65 | "prettier": "^2.4.1", 66 | "ts-jest": "^29.0.5", 67 | "ts-node": "^10.9.1", 68 | "typescript": "^5.2.2" 69 | }, 70 | "repository": { 71 | "type": "git", 72 | "url": "https://github.com/lighthouse-web3/encryption-sdk.git" 73 | }, 74 | "publishConfig": { 75 | "registry": "https://registry.npmjs.org" 76 | }, 77 | "keywords": [ 78 | "BLS", 79 | "ipfs", 80 | "encryption", 81 | "file-sharing", 82 | "access-control", 83 | "threshold", 84 | "keySharding", 85 | "keys", 86 | "lighthouse" 87 | ], 88 | "contributors": [ 89 | "Oki Ayobami (https://github.com/xlassix)", 90 | "Ravish Kumar Sharma (https://github.com/ravish1729)" 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /readme.MD: -------------------------------------------------------------------------------- 1 | # Kavach 2 | 3 | Kavach is an encryption SDK that allows you to build your trustless, decentralized and fault-tolerant Applications using distributed key shards with threshold cryptography 4 | 5 | ## Features 6 | 7 | - Randomized key shard generation 8 | - Shard Key support for privateKey and other security keys 9 | - Key Reconstruction from shards 10 | - Fully typed, support in TypeScript 11 | - Lighthouse Encryption Key storage(Optional 5 nodes) 12 | 13 | ## Install 14 | 15 | Just use your favorite package manager to add `lighthouse-kavach to your project: 16 | 17 | ```sh 18 | yarn add @lighthouse-web3/kavach 19 | 20 | npm i @lighthouse-web3/kavach 21 | 22 | ``` 23 | 24 | ## Methods 25 | 26 | - ### _generate ( threshold?: number, keyCount?: number)_ 27 | 28 | This method generates randomized key shards 29 | 30 | #### Parameters 31 | 32 | | Name | Type | Default | Description | 33 | | --------- | ---------------- | ------- | ----------------------------------------------------------------------------------------- | 34 | | threshold | number(optional) | 3 | minimum amount of key required to recover master key | 35 | | keyCount | number(optional) | 5 | number of shades to be generated (**Note**: _must be greater than or equal to threshold_) | 36 | 37 | #### returns 38 | 39 | | Name | Type | Description | 40 | | --------- | --------------------------- | --------------------- | 41 | | masterKey | string | 32 byte string or key | 42 | | keyShards | {key:string,index:string}[] | key shards | 43 | 44 | #### Demo 45 | 46 | ```javascript 47 | import { generate } from "@lighthouse-web3/kavach"; 48 | 49 | async function main() { 50 | const { masterKey, keyShards } = await generate(); 51 | console.log(`masterKey: ${masterKey}`); 52 | console.log(`keyShards:`, keyShards); 53 | } 54 | 55 | main() 56 | .then(() => process.exit(0)) 57 | .catch((error) => { 58 | console.error(error); 59 | process.exit(1); 60 | }); 61 | ``` 62 | 63 | - ### _recoverKey (keyShards: keyShard[])_ 64 | 65 | This method recovers the master key from the shards generated 66 | 67 | #### Parameters 68 | 69 | | Name | Type | Description | 70 | | -------- | --------------------------- | ---------------------------------------------------- | 71 | | keyShard | {key:string,index:string}[] | minimum amount of key required to recover master key | 72 | 73 | #### returns 74 | 75 | | Name | Type | Description | 76 | | --------- | ---------- | --------------------- | 77 | | masterKey | string | 32 byte string or key | 78 | | error | ErrorValue | null | 79 | 80 | #### Demo 81 | 82 | ```javascript 83 | import { generate, recoverKey } from "@lighthouse-web3/kavach"; 84 | 85 | async function main() { 86 | const { masterKey, keyShards } = await generate(); 87 | 88 | const { masterKey: recoveredKey } = await recoverKey(keyShards); 89 | console.log(masterKey === recoveredKey); //true 90 | } 91 | 92 | main() 93 | .then(() => process.exit(0)) 94 | .catch((error) => { 95 | console.error(error); 96 | process.exit(1); 97 | }); 98 | ``` 99 | 100 | - ### _shardKey (key: string,threshold?: number, keyCount?: number)_ 101 | 102 | shard existing Key into shards 103 | 104 | #### Parameters 105 | 106 | | Name | Type | Default | Description | 107 | | --------- | ---------------- | ------- | ----------------------------------------------------------------------------------------- | 108 | | key | string | | 32 byte string or key | 109 | | threshold | number(optional) | 3 | minimum amount of key required to recover master key | 110 | | keyCount | number(optional) | 5 | number of shades to be generated (**Note**: _must be greater than or equal to threshold_) | 111 | 112 | #### returns 113 | 114 | | Name | Type | Description | 115 | | ----------- | ---------- | --------------------------------------- | 116 | | isShardable | boolean | return true is the key could be sharded | 117 | | keyShards | keyShard[] | shards | 118 | 119 | #### Demo 120 | 121 | ```javascript 122 | import { shardKey, recoverKey } from "@lighthouse-web3/kavach"; 123 | 124 | async function main() { 125 | // known key customly generated or from ether random wallet privateKey 126 | // Note: Not all keys are shardable 127 | const knownKey = 128 | "554f886019b74852ab679258eb3cddf72f12f84dd6a946f8afc4283e48cc9467"; 129 | const { isShardable, keyShards } = await shardKey(knownKey); 130 | console.log(isShardable); // true 131 | 132 | //recover keys from shards 133 | const { masterKey } = await recoverKey(keyShards); 134 | 135 | //check if the key recovered was recovered 136 | console.log(masterKey === knownKey); //true 137 | } 138 | 139 | main() 140 | .then(() => process.exit(0)) 141 | .catch((error) => { 142 | console.error(error); 143 | process.exit(1); 144 | }); 145 | ``` 146 | 147 | - ### _saveShards( address: string, cid: string, auth_token: string, keyShards: keyShard[5] | any[5], shareTo : string[])_ 148 | 149 | Backup key to lighthouse's Node 150 | 151 | #### Parameters 152 | 153 | | Name | Type | Default | Description | 154 | | ---------- | --------------------------------- | ------- | --------------------------------------------- | 155 | | address | string | | address of the owner of the key | 156 | | cid | string | | unique id or file CID | 157 | | auth_token | string | | signed Message gotten from getAuthMessage/JWT | 158 | | keyShards | Array<{key:string; index:string}> | | An array of 5 key shards/ element | 159 | | shareTo | Array< address >(Optional) | [] | An array of address | 160 | 161 | #### returns 162 | 163 | | Name | Type | Description | 164 | | --------- | ---------- | -------------------- | 165 | | isSuccess | boolean | return true is saved | 166 | | error | ErrorValue | Errors | 167 | 168 | #### Demo 169 | 170 | ```javascript 171 | import { getAuthMessage, saveShards, generate } from "@lighthouse-web3/kavach"; 172 | import { ethers } from "ethers"; 173 | 174 | async function main() { 175 | const signer = new ethers.Wallet( 176 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 177 | ); 178 | 179 | const { masterKey, keyShards } = await generate(); 180 | 181 | const authMessage = await getAuthMessage(signer.address); 182 | const signedMessage = await signer.signMessage(authMessage.message); 183 | 184 | const { error, isSuccess } = await saveShards( 185 | signer.address, 186 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 187 | signedMessage, 188 | keyShards 189 | ); 190 | 191 | console.log(error === null); // true; 192 | console.log(isSuccess === true); //true; 193 | } 194 | 195 | main() 196 | .then(() => process.exit(0)) 197 | .catch((error) => { 198 | console.error(error); 199 | process.exit(1); 200 | }); 201 | ``` 202 | 203 | - ### _recoverShards( address: string, cid: string, auth_token: string, dynamicData:{},)_ 204 | 205 | recover key shards to lighthouse's Node 206 | 207 | #### Parameters 208 | 209 | | Name | Type | Default | Description | 210 | | ----------- | ----------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------- | 211 | | address | string | | address of the owner of the key | 212 | | cid | string | | unique id or file CID | 213 | | auth_token | string | | signed Message gotten from getAuthMessage/JWT | 214 | | keyCount | number(optional) | 3 | number of nodes to ping for shards (**Note**: _must be less than or equal to 5_) | 215 | | dynamicData | object<{[`conditionID`.`parameterName`]: value} >(Optional) | {} | This is used to pass additional or dynamic data like a signature during key recovery with AccessControl | 216 | 217 | #### returns 218 | 219 | | Name | Type | Description | 220 | | --------- | --------------------------------- | -------------------------------- | 221 | | keyShards | Array<{key:string; index:string}> | key shards recovered fromm nodes | 222 | | error | ErrorValue | Errors | 223 | 224 | #### Demo 225 | 226 | ```javascript 227 | import { 228 | getAuthMessage, 229 | saveShards, 230 | generate, 231 | recoverShards, 232 | } from "@lighthouse-web3/kavach"; 233 | import { ethers } from "ethers"; 234 | 235 | async function main() { 236 | const signer = new ethers.Wallet( 237 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 238 | ); 239 | 240 | const { masterKey, keyShards } = await generate(); 241 | 242 | let authMessage = await getAuthMessage(signer.address); 243 | let signedMessage = await signer.signMessage(authMessage.message); 244 | 245 | const { error, isSuccess } = await saveShards( 246 | signer.address, 247 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 248 | signedMessage, 249 | keyShards 250 | ); 251 | 252 | console.log(error === null); // true; 253 | console.log(isSuccess === true); //true; 254 | 255 | authMessage = await getAuthMessage(signer.address); 256 | signedMessage = await signer.signMessage(authMessage.message); 257 | //retrieve 3 keys 258 | const { error, shards } = await recoverShards( 259 | signer.address, 260 | cid, 261 | signedMessage, 262 | 3 263 | ); 264 | console.log(error == null); //true; 265 | console.log(shards.length === 3); // true; 266 | 267 | const { masterKey: recoveredKey } = await recoverKey(shards); 268 | console.log(masterKey === recoveredKey); //true 269 | } 270 | 271 | main() 272 | .then(() => process.exit(0)) 273 | .catch((error) => { 274 | console.error(error); 275 | process.exit(1); 276 | }); 277 | ``` 278 | 279 | - ### _shareToAddress(address: string, cid: string, auth_token: string, shareTo: Array )_ 280 | 281 | Share file Key to address 282 | 283 | #### Parameters 284 | 285 | | Name | Type | Default | Description | 286 | | ---------- | -------------------------- | ------- | ----------------------------------------------- | 287 | | address | string | | address of the owner of the key | 288 | | cid | string | | unique id or file CID | 289 | | auth_token | string | | signed Message gotten from getAuthMessage/ JWT | 290 | | shareTo | Array< address >(Optional) | [] | An array of address to share file key shards to | 291 | 292 | #### returns 293 | 294 | | Name | Type | Description | 295 | | --------- | ---------- | ------------------------- | 296 | | isSuccess | boolean | return true if successful | 297 | | error | ErrorValue | Errors | 298 | 299 | #### Demo 300 | 301 | ```javascript 302 | import { 303 | recoverShards, 304 | getAuthMessage, 305 | saveShards, 306 | AuthMessage, 307 | shareToAddress, 308 | generate, 309 | } from "@lighthouse-web3/kavach"; 310 | import { ethers } from "ethers"; 311 | 312 | async function main() { 313 | let signer = new ethers.Wallet( 314 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 315 | ); 316 | let signer2 = new ethers.Wallet( 317 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c99" 318 | ); 319 | const cid = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwAV"; 320 | const { masterKey, keyShards } = await generate(); 321 | 322 | //save file 323 | { 324 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 325 | const signedMessage = await signer.signMessage(authMessage.message); 326 | 327 | const { error, isSuccess } = await saveShards( 328 | signer.address, 329 | cid, 330 | signedMessage, 331 | keyShards 332 | ); 333 | console.log(error == null); //true; 334 | console.log(isSuccess == true); //true; 335 | } 336 | 337 | //share file key to address address 338 | { 339 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 340 | const signedMessage = await signer.signMessage(authMessage.message); 341 | const { error, isSuccess } = await shareToAddress( 342 | signer.address, 343 | cid, 344 | signedMessage, 345 | [signer2.address] 346 | ); 347 | 348 | console.log(error == null); // true; 349 | console.log(isSuccess == true); //true; 350 | } 351 | 352 | //recover shared from address shared to 353 | 354 | { 355 | const authMessage: AuthMessage = await getAuthMessage(signer2.address); 356 | const signedMessage = await signer2.signMessage(authMessage.message); 357 | 358 | //retrieve 3 keys 359 | const { error, shards } = await recoverShards( 360 | signer2.address, 361 | cid, 362 | signedMessage, 363 | 3 364 | ); 365 | console.log(error == null); //true; 366 | console.log(shards.length === 3); // true; 367 | } 368 | } 369 | 370 | main() 371 | .then(() => process.exit(0)) 372 | .catch((error) => { 373 | console.error(error); 374 | process.exit(1); 375 | }); 376 | ``` 377 | 378 | - ### _revokeAccess(address: string, cid: string, auth_token: string, revokeTo: Array )_ 379 | 380 | revoke access to addresses with direct access 381 | 382 | #### Parameters 383 | 384 | | Name | Type | Default | Description | 385 | | ---------- | -------------------------- | ------- | ----------------------------------------------- | 386 | | address | string | | address of the owner of the key | 387 | | cid | string | | unique id or file CID | 388 | | auth_token | string | | signed Message gotten from getAuthMessage /JWT | 389 | | revokeTo | Array< address >(Optional) | [] | An array of address to remove for Direct access | 390 | 391 | #### returns 392 | 393 | | Name | Type | Description | 394 | | --------- | ---------- | ------------------------- | 395 | | isSuccess | boolean | return true if successful | 396 | | error | ErrorValue | Errors | 397 | 398 | #### Demo 399 | 400 | ```javascript 401 | import { 402 | recoverShards, 403 | getAuthMessage, 404 | saveShards, 405 | AuthMessage, 406 | revokeAccess, 407 | generate, 408 | } from "@lighthouse-web3/kavach"; 409 | import { ethers } from "ethers"; 410 | 411 | async function main() { 412 | let signer = new ethers.Wallet( 413 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 414 | ); 415 | let signer2 = new ethers.Wallet( 416 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c99" 417 | ); 418 | const cid = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwVV"; 419 | const { masterKey, keyShards } = await generate(); 420 | 421 | //save file 422 | { 423 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 424 | const signedMessage = await signer.signMessage(authMessage.message); 425 | 426 | const { error, isSuccess } = await saveShards( 427 | signer.address, 428 | cid, 429 | signedMessage, 430 | keyShards, 431 | [ 432 | "0x95CF5354519a6ad2bD7e53fe7763201dfB24bFE4", 433 | "0xb46D27B3BfC07D27702EBddbe197Fc9276b70581", 434 | signer2.address, 435 | ] 436 | ); 437 | console.log(error == null); //true; 438 | console.log(isSuccess == true); //true; 439 | } 440 | 441 | //recover shared from address shared to 442 | 443 | { 444 | const authMessage: AuthMessage = await getAuthMessage(signer2.address); 445 | const signedMessage = await signer2.signMessage(authMessage.message); 446 | 447 | //retrieve 3 keys 448 | const { error, shards } = await recoverShards( 449 | signer2.address, 450 | cid, 451 | signedMessage, 452 | 3 453 | ); 454 | console.log(error == null); //true; 455 | console.log(shards.length === 3); // true; 456 | } 457 | 458 | //revoke access to direct shared address 459 | { 460 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 461 | const signedMessage = await signer.signMessage(authMessage.message); 462 | const { error, isSuccess } = await revokeAccess( 463 | signer.address, 464 | cid, 465 | signedMessage, 466 | [signer2.address] 467 | ); 468 | 469 | console.log(error == null); // true; 470 | console.log(isSuccess == true); //true; 471 | } 472 | 473 | //recover shared from address shared to 474 | 475 | { 476 | const authMessage: AuthMessage = await getAuthMessage(signer2.address); 477 | const signedMessage = await signer2.signMessage(authMessage.message); 478 | 479 | //retrieve 3 keys 480 | const { error, shards } = await recoverShards( 481 | signer2.address, 482 | cid, 483 | signedMessage, 484 | 3 485 | ); 486 | console.log(error); // { message: "you don't have access", data: {} }; 487 | console.log(shards.length === 0); // true ; 488 | } 489 | } 490 | 491 | main() 492 | .then(() => process.exit(0)) 493 | .catch((error) => { 494 | console.error(error); 495 | process.exit(1); 496 | }); 497 | ``` 498 | 499 | - ### _accessCondition( address: string, cid: string, auth_token: string, conditions: Condition[], aggregator?: string,chainType?: ChainType, keyShards? : Array<{key:string; index:string}>, decryptionType? : string )_ 500 | 501 | Add more granular access Conditions based on on-Chain Data, this supports custom EVM contracts, block timestamps and so on. 502 | with support for over 15 Ethereum Virtual Machine (EVM) based networks and based Solana RPC calls 503 | 504 | - Ethereum 505 | - Rinkeby 506 | - Polygon 507 | - Fantom 508 | - FantomTest 509 | - AVAX 510 | - Fuji 511 | - BSC 512 | - BSCTest 513 | - Optimism 514 | - OptimismGoerli 515 | - OptimismKovan 516 | - Mumbai 517 | - FVM 518 | - Wallaby 519 | - Calibration 520 | - Shardeum 521 | - Goerli 522 | - Hyperspace 523 | - BTTC 524 | - BTTC_Testnet 525 | - Sepolia_PGN 526 | - Arbitrum_Sepolia 527 | - Sepolia: 528 | - BASE_Goerli 529 | 530 | Solana 531 | 532 | - DEVNET 533 | - TESTNET 534 | - MAINNET 535 | 536 | #### Parameters 537 | 538 | | Name | Type | Description | 539 | | --------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | 540 | | address | string | Address of the owner of the key | 541 | | cid | string | Unique id or file CID | 542 | | auth_token | string | Signed Message gotten from getAuthMessage /JWT | 543 | | conditions | Array< Condition > | This Array contains a list of conditions to be tested on chain | 544 | | aggregator | string | This is a template string that structures how the conditions should be computed | 545 | | chainType | string | This defaults to EVM and can be set to Solana for Solana conditions | 546 | | keyShards? | Array<{key:string; index:string}> | This Field is optional, you can use it to set, overWrite or rotate key shards | 547 | | decryptionType? | string | This value can be set to ACCESS_CONDITIONS to first time shard is added, **WARNING: This sets Owner to address zero(0x0000000000000000000000000000)** | 548 | 549 | #### returns 550 | 551 | | Name | Type | Description | 552 | | --------- | ---------- | ------------------------- | 553 | | isSuccess | boolean | return true if successful | 554 | | error | ErrorValue | Errors | 555 | 556 | #### Demo 557 | 558 | ```javascript 559 | import { 560 | recoverShards, 561 | getAuthMessage, 562 | saveShards, 563 | AuthMessage, 564 | accessControl, 565 | generate, 566 | } from "@lighthouse-web3/kavach"; 567 | import { ethers } from "ethers"; 568 | 569 | async function main() { 570 | let signer = new ethers.Wallet( 571 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 572 | ); 573 | let signer2 = new ethers.Wallet( 574 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c99" 575 | ); 576 | const cid = "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwVM"; 577 | 578 | //save file 579 | { 580 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 581 | const signedMessage = await signer.signMessage(authMessage.message); 582 | const { masterKey, keyShards } = await generate(); 583 | 584 | const { error, isSuccess } = await saveShards( 585 | signer.address, 586 | cid, 587 | signedMessage, 588 | keyShards 589 | ); 590 | console.log(error == null); //true; 591 | console.log(isSuccess == true); //true; 592 | } 593 | 594 | // add access control to cid direct shared address 595 | { 596 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 597 | const signedMessage = await signer.signMessage(authMessage.message); 598 | const { error, isSuccess } = await accessControl( 599 | signer.address, 600 | cid, 601 | signedMessage, 602 | [ 603 | { 604 | id: 3, 605 | chain: "Polygon", 606 | method: "getBlockNumber", 607 | standardContractType: "", 608 | returnValueTest: { comparator: ">=", value: "0" }, 609 | }, 610 | { 611 | id: 2, 612 | chain: "Optimism", 613 | method: "getBalance", 614 | standardContractType: "", 615 | returnValueTest: { comparator: ">=", value: "0" }, 616 | }, 617 | { 618 | id: 1, 619 | chain: "FantomTest", 620 | method: "balanceOf", 621 | standardContractType: "ERC20", 622 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 623 | returnValueTest: { comparator: ">=", value: "0" }, 624 | parameters: [":userAddress"], 625 | }, 626 | ], 627 | "([2] and [1]) or [3]" 628 | ); 629 | console.log(error == null); 630 | console.log(isSuccess == true); 631 | } 632 | 633 | // recover shared from an address that matches the above condition 634 | // that is 635 | // has a balance equal to or greater than Zero on the Optimism mainnet and has a token balance greater than equal to zero of the token 0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1 on fantom's testnet 636 | // or if block height is greater than zero 637 | 638 | { 639 | const authMessage: AuthMessage = await getAuthMessage(signer2.address); 640 | const signedMessage = await signer2.signMessage(authMessage.message); 641 | console.log(signer2.address); 642 | 643 | //retrieve 3 keys 644 | const { error, shards } = await recoverShards( 645 | signer2.address, 646 | cid, 647 | signedMessage, 648 | 3, 649 | dynamicData 650 | ); 651 | console.log(error == null); //true; 652 | console.log(shards.length === 3); // true; 653 | } 654 | } 655 | 656 | main() 657 | .then(() => process.exit(0)) 658 | .catch((error) => { 659 | console.error(error); 660 | process.exit(1); 661 | }); 662 | ``` 663 | 664 | ## Auth Methods 665 | 666 | - ### _getAuthMessage( address: string)_ 667 | 668 | Get Consensus Message to Sign 669 | 670 | #### Parameters 671 | 672 | | Name | Type | Default | Description | 673 | | ------- | ------ | ------- | ------------------------------- | 674 | | address | string | | address of the owner of the key | 675 | 676 | #### returns 677 | 678 | | Name | Type | Description | 679 | | ------- | ---------- | ------------------------ | 680 | | message | string | return consensus message | 681 | | error | ErrorValue | Errors | 682 | 683 | ```javascript 684 | import { getAuthMessage, AuthMessage } from "@lighthouse-web3/kavach"; 685 | import { ethers } from "ethers"; 686 | 687 | async function main() { 688 | let signer = new ethers.Wallet( 689 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 690 | ); 691 | 692 | // get consensus message 693 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 694 | const signedMessage = await signer.signMessage(authMessage.message); 695 | 696 | console.log(typeof authMessage.message == "string"); //true; 697 | console.log(authMessage.error == null); //true; 698 | } 699 | 700 | main() 701 | .then(() => process.exit(0)) 702 | .catch((error) => { 703 | console.error(error); 704 | process.exit(1); 705 | }); 706 | ``` 707 | 708 | - ### _getJWT(address:string,signedMessage: SignedMessage)_ 709 | 710 | Get Consensus Message to Sign 711 | 712 | #### Parameters 713 | 714 | | Name | Type | Default | Description | 715 | | ----------------- | ------- | ------- | ----------------------------------------------------- | 716 | | address | string | | address of the owner of the key | 717 | | payload | string | | signed consensus message or refresh Token | 718 | | useAsRefreshToken | boolean | false | If payload is refreshToken this should be set to true | 719 | 720 | #### returns 721 | 722 | | Name | Type | Description | 723 | | ------------ | ---------- | ----------- | 724 | | JWT | string | return JWT | 725 | | refreshToken | string | | 726 | | error | ErrorValue | Errors | 727 | 728 | ```javascript 729 | import { getAuthMessage, AuthMessage, getJWT } from "@lighthouse-web3/kavach"; 730 | import { ethers } from "ethers"; 731 | 732 | async function main() { 733 | let signer = new ethers.Wallet( 734 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 735 | ); 736 | 737 | // get consensus message 738 | const authMessage: AuthMessage = await getAuthMessage(signer.address); 739 | const signedMessage = await signer.signMessage(authMessage.message); 740 | 741 | const { JWT, error } = await getJWT(signer.address, signedMessage); 742 | console.log(typeof JWT == "string"); //true; 743 | console.log(error == null); //true; 744 | } 745 | 746 | main() 747 | .then(() => process.exit(0)) 748 | .catch((error) => { 749 | console.error(error); 750 | process.exit(1); 751 | }); 752 | ``` 753 | 754 | - ### _transferOwnership(address: string, cid: string, newOwner: string, auth_token: string, resetSharedTo: boolean = true)_ 755 | 756 | Transfer Ownership of a Resource 757 | 758 | #### Parameters 759 | 760 | | Name | Type | Default | Description | 761 | | ------------- | ------- | ------- | ----------------------------------------------- | 762 | | address | string | | Address of the current owner of the resource | 763 | | cid | string | | Content ID (CID) of the resource | 764 | | newOwner | string | | Address of the new owner for the resource | 765 | | auth_token | string | | Authentication payload or token | 766 | | resetSharedTo | boolean | true | Reset shared permissions when ownership changes | 767 | 768 | #### Returns 769 | 770 | | Name | Type | Description | 771 | | ------ | ------ | -------------------------------- | 772 | | result | string | Result of the ownership transfer | 773 | | error | Error | Any error that occurs | 774 | 775 | #### Example 776 | 777 | ```javascript 778 | import { transferOwnership } from "@lighthouse-web3/kavach"; 779 | 780 | // Example usage of transferOwnership function 781 | async function main() { 782 | const currentOwner = "0x1234567890abcdef"; 783 | const resourceId = "QmXyZAbCdEfGhIjK"; 784 | const newOwner = "0x9876543210fedcba"; 785 | const authPayload = "your-authentication-token"; 786 | 787 | const { result, error } = await transferOwnership( 788 | currentOwner, 789 | resourceId, 790 | newOwner, 791 | authPayload 792 | ); 793 | 794 | if (error) { 795 | console.error("Error:", error); 796 | } else { 797 | console.log("Ownership transfer result:", result); 798 | } 799 | } 800 | 801 | main() 802 | .then(() => process.exit(0)) 803 | .catch((error) => { 804 | console.error(error); 805 | process.exit(1); 806 | }); 807 | ``` 808 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | const defaultConfig = { 2 | lighthouseBLSNode: "https://encryption.lighthouse.storage", 3 | lighthouseAuthNode: "https://encryption.lighthouse.storage", 4 | lighthouseBLSNodeDev: "http://enctest.lighthouse.storage", 5 | isDev: false, 6 | }; 7 | 8 | export { defaultConfig } 9 | 10 | export default defaultConfig; -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "./methods/generate" 2 | import { getJWT } from "./methods/getJWT" 3 | import { getAuthMessage } from "./methods/getAuthMessage" 4 | import { recoverKey } from "./methods/recoverKey" 5 | import { recoverShards } from "./methods/recoverShards" 6 | import { saveShards } from "./methods/saveShards" 7 | import { shardKey } from "./methods/shardKey" 8 | import { revokeAccess } from "./methods/revokeAccess" 9 | import { accessControl } from "./methods/accessControl" 10 | import { shareToAddress } from "./methods/shareToAddress" 11 | import { transferOwnership } from "./methods/transferOwnership" 12 | import { getAccessCondition } from "./methods/getAccessCondition" 13 | 14 | export { 15 | generate, 16 | getJWT, 17 | getAuthMessage, 18 | recoverShards, 19 | revokeAccess, 20 | recoverKey, 21 | saveShards, 22 | shardKey, 23 | accessControl, 24 | shareToAddress, 25 | transferOwnership, 26 | getAccessCondition 27 | } 28 | 29 | export default { 30 | generate, 31 | getJWT, 32 | getAuthMessage, 33 | recoverShards, 34 | revokeAccess, 35 | recoverKey, 36 | saveShards, 37 | shardKey, 38 | accessControl, 39 | shareToAddress, 40 | transferOwnership, 41 | getAccessCondition 42 | } -------------------------------------------------------------------------------- /src/methods/accessControl/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AuthToken, 3 | Condition, 4 | DecryptionType, 5 | KeyShard, 6 | ChainType, 7 | LightHouseSDKResponse, 8 | } from "../../types"; 9 | import config from "../../config"; 10 | import { updateConditionSchema, accessConditionSchema } from "./validator"; 11 | import { isEqual, isCidReg, API_NODE_HANDLER } from "../../util/index"; 12 | 13 | export const accessControl = async ( 14 | address: string, 15 | cid: string, 16 | auth_token: AuthToken, 17 | conditions: Condition[], 18 | aggregator?: string, 19 | chainType: ChainType = "evm", 20 | keyShards: KeyShard[] = [], 21 | decryptionType: DecryptionType = "ADDRESS" 22 | ): Promise => { 23 | try { 24 | if ( 25 | !Array.isArray(keyShards) || 26 | (keyShards.length != 5 && keyShards.length != 0) 27 | ) { 28 | throw new Error("keyShards must be an array of 5 objects"); 29 | } 30 | if (!isCidReg(cid)) { 31 | throw new Error("Invalid CID"); 32 | } 33 | const { error } = 34 | keyShards.length == 5 35 | ? accessConditionSchema.validate({ 36 | address, 37 | cid, 38 | conditions, 39 | aggregator, 40 | decryptionType, 41 | chainType, 42 | keyShards, 43 | }) 44 | : updateConditionSchema.validate({ 45 | address, 46 | cid, 47 | conditions, 48 | aggregator, 49 | chainType, 50 | }); 51 | 52 | if (error) { 53 | throw new Error(`Condition validation error: ${error.message}`); 54 | } 55 | const nodeId = [1, 2, 3, 4, 5]; 56 | const nodeUrl = nodeId.map((elem) => 57 | config.isDev 58 | ? `:900${elem}/api/fileAccessConditions/${elem}` 59 | : `/api/fileAccessConditions/${elem}` 60 | ); 61 | // send encryption key 62 | const requestData = async (url: any, index: any) => { 63 | try { 64 | return keyShards.length === 5 65 | ? await API_NODE_HANDLER(url, "POST", auth_token, { 66 | address, 67 | cid, 68 | conditions, 69 | aggregator, 70 | decryptionType, 71 | chainType, 72 | payload: keyShards[index], 73 | }) 74 | : await API_NODE_HANDLER(url, "PUT", auth_token, { 75 | address, 76 | cid, 77 | conditions, 78 | aggregator, 79 | chainType, 80 | }); 81 | } catch (error: any) { 82 | return { 83 | isSuccess: false, 84 | error: JSON.parse(error.message), 85 | }; 86 | } 87 | }; 88 | const data = []; 89 | for (const [index, url] of nodeUrl.entries()) { 90 | const response = await requestData(url, index); 91 | if (response.error) 92 | await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second before retrying 93 | 94 | data.push(response); 95 | } 96 | return { 97 | isSuccess: 98 | isEqual(...data.map((e) => e.message)) && 99 | data[0]?.message === "success", 100 | error: null, 101 | }; 102 | } catch (err: any) { 103 | return { isSuccess: false, error: JSON.parse(err?.message) }; 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /src/methods/accessControl/test/accessControl.spec.ts: -------------------------------------------------------------------------------- 1 | import _package from "../../../index"; 2 | import { ethers } from "ethers"; 3 | import { getAuthMessage } from "../../getAuthMessage"; 4 | 5 | describe("AccessControl", () => { 6 | let signer; 7 | 8 | beforeAll(async () => { 9 | signer = new ethers.Wallet( 10 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b", 11 | ); 12 | }); 13 | 14 | test("Invalid Condition", async () => { 15 | const { error } = await _package.accessControl( 16 | signer.address, 17 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 18 | "swrwwr", 19 | [ 20 | { 21 | id: 1, 22 | chain: "FantomTes", 23 | method: "balanceOf", 24 | standardContractType: "ERC20", 25 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 26 | returnValueTest: { comparator: ">=", value: "0" }, 27 | parameters: [":userAddress"], 28 | }, 29 | { 30 | id: 1, 31 | chain: "FantomTest", 32 | method: "balanceOf", 33 | standardContractType: "ERC20", 34 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 35 | returnValueTest: { comparator: ">=", value: "0" }, 36 | parameters: [":userAddress"], 37 | }, 38 | ], 39 | "([2] and [1])" 40 | ); 41 | expect(typeof error).toBe("string"); 42 | expect(error).toMatch(/Condition validation error:/); 43 | }, 20000); 44 | 45 | test("Invalid Signature", async () => { 46 | const { error } = await _package.accessControl( 47 | signer.address, 48 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 49 | "swrwwr", 50 | [ 51 | { 52 | id: 1, 53 | chain: "FantomTest", 54 | method: "balanceOf", 55 | standardContractType: "ERC20", 56 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 57 | returnValueTest: { comparator: ">=", value: "0" }, 58 | parameters: [":userAddress"], 59 | }, 60 | { 61 | id: 2, 62 | chain: "FantomTest", 63 | method: "balanceOf", 64 | standardContractType: "ERC20", 65 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 66 | returnValueTest: { comparator: ">=", value: "0" }, 67 | parameters: [":userAddress"], 68 | }, 69 | ], 70 | "([2] and [1])" 71 | ); 72 | expect(typeof error?.message).toBe("string"); 73 | expect(error?.message).toMatch(/invalid signature/i); 74 | }, 20000); 75 | 76 | test("data Conditions", async () => { 77 | const authMessage = await getAuthMessage(signer.address); 78 | const signedMessage = await signer.signMessage(authMessage.message); 79 | const { error, isSuccess } = await _package.accessControl( 80 | signer.address, 81 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 82 | signedMessage, 83 | [ 84 | { 85 | id: 1, 86 | chain: "FantomTest", 87 | method: "balanceOf", 88 | standardContractType: "ERC20", 89 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 90 | returnValueTest: { comparator: ">=", value: "0" }, 91 | parameters: [":userAddress"], 92 | }, 93 | { 94 | id: 2, 95 | chain: "FantomTest", 96 | method: "balanceOf", 97 | standardContractType: "ERC20", 98 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 99 | returnValueTest: { comparator: ">=", value: "0" }, 100 | parameters: [":userAddress"], 101 | }, 102 | ], 103 | "([2] and [1])" 104 | ); 105 | expect(error).toBe(null); 106 | expect(isSuccess).toBe(true); 107 | }, 20000); 108 | 109 | test("Add new cid access Conditions", async () => { 110 | const authMessage = await getAuthMessage(signer.address); 111 | const signedMessage = await signer.signMessage(authMessage.message); 112 | const { masterKey, keyShards } = await _package.generate(3, 5) 113 | const { error, isSuccess } = await _package.accessControl( 114 | signer.address, 115 | "QmPzhJDbMgoxXH7JoRc1roXqkLGtngLiGVhegiDEmmTnbM", 116 | signedMessage, 117 | [ 118 | { 119 | id: 1, 120 | chain: "FantomTest", 121 | method: "balanceOf", 122 | standardContractType: "ERC20", 123 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 124 | returnValueTest: { comparator: ">=", value: "0" }, 125 | parameters: [":userAddress"], 126 | }, 127 | { 128 | id: 2, 129 | chain: "FantomTest", 130 | method: "balanceOf", 131 | standardContractType: "ERC20", 132 | contractAddress: "0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1", 133 | returnValueTest: { comparator: ">=", value: "0" }, 134 | parameters: [":userAddress"], 135 | }, 136 | ], 137 | "([2] and [1])", 138 | "EVM", 139 | keyShards, 140 | "ADDRESS", 141 | ); 142 | expect(error).toBe(null); 143 | expect(isSuccess).toBe(true); 144 | }, 20000); 145 | }); 146 | -------------------------------------------------------------------------------- /src/methods/accessControl/validator.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | const solidityType = [ 4 | "address", 5 | "address[]", 6 | "bool", 7 | "bool[]", 8 | "bytes1", 9 | "bytes2", 10 | "bytes3", 11 | "bytes4", 12 | "bytes5", 13 | "bytes6", 14 | "bytes7", 15 | "bytes8", 16 | "bytes16", 17 | "bytes32", 18 | "bytes1[]", 19 | "bytes2[]", 20 | "bytes3[]", 21 | "bytes4[]", 22 | "bytes5[]", 23 | "bytes6[]", 24 | "bytes7[]", 25 | "bytes8[]", 26 | "bytes16[]", 27 | "bytes32[]", 28 | "uint8", 29 | "uint16", 30 | "uint24", 31 | "uint32", 32 | "uint40", 33 | "uint48", 34 | "uint64", 35 | "uint128", 36 | "uint192", 37 | "uint256", 38 | "int8", 39 | "int16", 40 | "int24", 41 | "int32", 42 | "int40", 43 | "int48", 44 | "int64", 45 | "int128", 46 | "int192", 47 | "int256", 48 | "uint8[]", 49 | "uint16[]", 50 | "uint24[]", 51 | "uint32[]", 52 | "uint40[]", 53 | "uint48[]", 54 | "uint64[]", 55 | "uint128[]", 56 | "uint192[]", 57 | "uint256[]", 58 | "int8[]", 59 | "int16[]", 60 | "int24[]", 61 | "int32[]", 62 | "int40[]", 63 | "int48[]", 64 | "int64[]", 65 | "int128[]", 66 | "int192[]", 67 | "int256[]", 68 | ]; 69 | 70 | const SupportedChains = { 71 | EVM: [], 72 | SOLANA: ["DEVNET", "TESTNET", "MAINNET"], 73 | COREUM: ["Coreum_Devnet", "Coreum_Testnet", "Coreum_Mainnet"], 74 | RADIX: ["Radix_Mainnet"], 75 | }; 76 | 77 | const evmConditions = Joi.array() 78 | .required() 79 | .items( 80 | Joi.object({ 81 | id: Joi.number().min(1).required(), 82 | standardContractType: Joi.string() 83 | .valid("ERC20", "ERC721", "ERC1155", "Custom", "") 84 | .insensitive() 85 | .required(), 86 | contractAddress: Joi.when("standardContractType", { 87 | is: Joi.equal(""), 88 | then: Joi.string(), 89 | otherwise: Joi.string().required(), 90 | }), 91 | chain: Joi.string().insensitive().required(), 92 | method: Joi.when("standardContractType", { 93 | is: Joi.equal(""), 94 | then: Joi.string().valid("getBalance", "getBlockNumber").required(), 95 | otherwise: Joi.string().required(), 96 | }), 97 | parameters: Joi.when("standardContractType", { 98 | is: Joi.equal(""), 99 | then: Joi.array(), 100 | otherwise: Joi.array().required(), 101 | }), 102 | returnValueTest: Joi.object({ 103 | comparator: Joi.string() 104 | .valid("==", ">=", "<=", "!=", ">", "<") 105 | .required(), 106 | value: Joi.alternatives( 107 | Joi.number(), 108 | Joi.string(), 109 | Joi.array() 110 | ).required(), 111 | }).required(), 112 | inputArrayType: Joi.when("standardContractType", { 113 | is: Joi.equal("Custom"), 114 | then: Joi.array() 115 | .items(Joi.string().valid(...solidityType)) 116 | .required(), 117 | }), 118 | outputType: Joi.when("standardContractType", { 119 | is: Joi.equal("Custom"), 120 | then: Joi.string() 121 | .valid(...solidityType) 122 | .required(), 123 | }), 124 | }) 125 | ) 126 | .unique((a, b) => a.id === b.id); 127 | 128 | const solanaConditions = Joi.array() 129 | .required() 130 | .items( 131 | Joi.object({ 132 | id: Joi.number().min(1).required(), 133 | contractAddress: Joi.when("standardContractType", { 134 | is: Joi.equal(""), 135 | then: Joi.string(), 136 | otherwise: Joi.string().required(), 137 | }), 138 | chain: Joi.string() 139 | .valid(...SupportedChains["SOLANA"]) 140 | .insensitive() 141 | .required(), 142 | method: Joi.when("standardContractType", { 143 | is: Joi.equal(""), 144 | then: Joi.string() 145 | .valid("getBalance", "getLastBlockTime", "getBlockHeight") 146 | .required(), 147 | otherwise: Joi.string().valid("getTokenAccountsByOwner").required(), 148 | }), 149 | standardContractType: Joi.string() 150 | .valid("spl-token", "") 151 | .insensitive() 152 | .required(), 153 | parameters: Joi.when("standardContractType", { 154 | is: Joi.equal(""), 155 | then: Joi.array(), 156 | otherwise: Joi.array(), 157 | }), 158 | pdaInterface: Joi.object({ 159 | offset: Joi.number().min(0), 160 | selector: Joi.string(), 161 | }).required(), 162 | returnValueTest: Joi.object({ 163 | comparator: Joi.string() 164 | .valid("==", ">=", "<=", "!=", ">", "<") 165 | .required(), 166 | value: Joi.alternatives( 167 | Joi.number(), 168 | Joi.string(), 169 | Joi.array() 170 | ).required(), 171 | }).required(), 172 | }) 173 | ) 174 | .unique((a, b) => a.id === b.id); 175 | 176 | const coreumConditions = Joi.array() 177 | .required() 178 | .items( 179 | Joi.object({ 180 | id: Joi.number().min(1).required(), 181 | contractAddress: Joi.when("standardContractType", { 182 | is: Joi.equal(""), 183 | then: Joi.string(), 184 | otherwise: Joi.string().required(), 185 | }), 186 | denom: Joi.string(), 187 | classid: Joi.string(), 188 | standardContractType: Joi.string().allow(""), 189 | chain: Joi.string() 190 | .valid(...SupportedChains["COREUM"]) 191 | .insensitive() 192 | .required(), 193 | method: Joi.when("standardContractType", { 194 | is: Joi.equal(""), 195 | then: Joi.string().required(), 196 | otherwise: Joi.string().required(), 197 | }), 198 | parameters: Joi.when("standardContractType", { 199 | is: Joi.equal(""), 200 | then: Joi.array(), 201 | otherwise: Joi.array().required(), 202 | }), 203 | returnValueTest: Joi.object({ 204 | comparator: Joi.string() 205 | .valid("==", ">=", "<=", "!=", ">", "<") 206 | .required(), 207 | value: Joi.alternatives( 208 | Joi.number(), 209 | Joi.string(), 210 | Joi.array() 211 | ).required(), 212 | }).required(), 213 | }) 214 | ) 215 | .unique((a, b) => a.id === b.id); 216 | 217 | const radixConditions = Joi.array() 218 | .required() 219 | .items( 220 | Joi.object({ 221 | id: Joi.number().min(1).required(), 222 | standardContractType: Joi.string().allow(""), 223 | resourceAddress: Joi.string().required(), 224 | chain: Joi.string() 225 | .valid(...SupportedChains["RADIX"]) 226 | .insensitive() 227 | .required(), 228 | method: Joi.when("standardContractType", { 229 | is: Joi.equal(""), 230 | then: Joi.string().required(), 231 | otherwise: Joi.string().required(), 232 | }), 233 | returnValueTest: Joi.object({ 234 | comparator: Joi.string() 235 | .valid("==", ">=", "<=", "!=", ">", "<") 236 | .required(), 237 | value: Joi.alternatives( 238 | Joi.number(), 239 | Joi.string(), 240 | Joi.array() 241 | ).required(), 242 | }).required(), 243 | }) 244 | ) 245 | .unique((a, b) => a.id === b.id); 246 | 247 | const updateConditionSchema = Joi.object({ 248 | chainType: Joi.string() 249 | .allow("", null) 250 | .empty(["", null]) 251 | .default("EVM") 252 | .valid("EVM", "SOLANA", "COREUM", "RADIX") 253 | .insensitive(), 254 | conditions: Joi.when("chainType", { 255 | is: Joi.equal("EVM"), 256 | then: evmConditions, 257 | otherwise: Joi.when("chainType", { 258 | is: Joi.equal("SOLANA"), 259 | then: solanaConditions, 260 | otherwise: Joi.when("chainType", { 261 | is: Joi.equal("COREUM"), 262 | then: coreumConditions, 263 | otherwise: radixConditions, 264 | }), 265 | }), 266 | }), 267 | decryptionType: Joi.string() 268 | .allow("", null) 269 | .empty(["", null]) 270 | .default("ADDRESS") 271 | .valid("ADDRESS", "ACCESS_CONDITIONS") 272 | .insensitive(), 273 | address: Joi.string().required(), 274 | cid: Joi.string().required(), 275 | // TO aggregator next iteration: "1 or 2 and (3 xor 4)" 276 | aggregator: Joi.when("conditions.length", { 277 | is: Joi.number().greater(1), 278 | then: Joi.string() 279 | .pattern(/( and | or )/i) 280 | .required(), 281 | }), 282 | }); 283 | 284 | const accessConditionSchema = Joi.object({ 285 | chainType: Joi.string() 286 | .allow("", null) 287 | .empty(["", null]) 288 | .default("EVM") 289 | .valid("EVM", "SOLANA", "COREUM", "RADIX") 290 | .insensitive(), 291 | decryptionType: Joi.string() 292 | .allow("", null) 293 | .empty(["", null]) 294 | .default("ADDRESS") 295 | .valid("ADDRESS", "ACCESS_CONDITIONS") 296 | .insensitive(), 297 | conditions: Joi.when("chainType", { 298 | is: Joi.equal("EVM"), 299 | then: evmConditions, 300 | otherwise: Joi.when("chainType", { 301 | is: Joi.equal("SOLANA"), 302 | then: solanaConditions, 303 | otherwise: Joi.when("chainType", { 304 | is: Joi.equal("COREUM"), 305 | then: coreumConditions, 306 | otherwise: radixConditions, 307 | }), 308 | }), 309 | }), 310 | address: Joi.string().required(), 311 | keyShards: Joi.array().min(5).max(5).required().items(Joi.object()), 312 | cid: Joi.string().required(), 313 | // TO aggregator next iteration: "1 or 2 and (3 xor 4)" 314 | aggregator: Joi.when("conditions.length", { 315 | is: Joi.number().greater(1), 316 | then: Joi.string() 317 | .pattern(/( and | or )/i) 318 | .required(), 319 | }), 320 | }); 321 | export { updateConditionSchema, accessConditionSchema }; 322 | -------------------------------------------------------------------------------- /src/methods/generate/index.ts: -------------------------------------------------------------------------------- 1 | import { GeneratedKey } from "../../types"; 2 | 3 | let bls: any = null; 4 | if (typeof window === "undefined") { 5 | bls = eval("require")("bls-eth-wasm"); 6 | } else { 7 | bls = require("bls-eth-wasm/browser"); 8 | } 9 | 10 | export const generate = async ( 11 | threshold = 3, 12 | keyCount = 5 13 | ): Promise => { 14 | if (threshold > keyCount) { 15 | throw new Error("keyCount must be greater then threshold"); 16 | } 17 | 18 | const msk: any = []; 19 | const idVec: any = []; 20 | const secVec: any = []; 21 | 22 | await bls.init(bls.BLS12_381).then(() => bls.getCurveOrder()); 23 | 24 | /* 25 | setup master secret key 26 | */ 27 | 28 | // other members of the array ingredients used in the algorithm 29 | for (let i = 0; i < threshold; i++) { 30 | const sk = new bls.SecretKey(); 31 | sk.setByCSPRNG(); 32 | msk.push(sk); 33 | } 34 | 35 | /* 36 | key sharing 37 | */ 38 | for (let i = 0; i < keyCount; i++) { 39 | //create random Vector ID(points on the ECC) 40 | const id = new bls.Id(); 41 | id.setByCSPRNG(); 42 | idVec.push(id); 43 | 44 | //Create a secKey Shard 45 | const sk = new bls.SecretKey(); 46 | sk.share(msk, idVec[i]); 47 | secVec.push(sk); 48 | } 49 | 50 | if (secVec.length !== idVec.length) 51 | throw new Error("key vector Length don't match"); 52 | 53 | //Convert vector in to readable hex values 54 | return { 55 | masterKey: msk[0]?.serializeToHexStr(), 56 | keyShards: 57 | secVec?.map((sk: any, index: any) => ({ 58 | key: sk.serializeToHexStr(), 59 | index: idVec[index].serializeToHexStr(), 60 | })) ?? [], 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /src/methods/generate/test/generate.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | 3 | test("Generate Key", async () => { 4 | const { masterKey, keyShards } = await _package.generate(); 5 | expect(typeof masterKey).toBe("string"); 6 | expect(keyShards.length).toBe(5); 7 | }, 20000); 8 | 9 | -------------------------------------------------------------------------------- /src/methods/getAccessCondition/index.ts: -------------------------------------------------------------------------------- 1 | import defaultConfig from "../../config"; 2 | import { IGetAccessCondition } from "../../types"; 3 | import { API_NODE_HANDLER } from "../../util"; 4 | 5 | 6 | export const getAccessCondition = async (cid: string): Promise<{ data: IGetAccessCondition }> => { 7 | try { 8 | const conditions = await API_NODE_HANDLER(`/api/fileAccessConditions/get/${cid}`, "GET") 9 | return { data: conditions } 10 | } catch (error: any) { 11 | throw new Error(error.message) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/methods/getAccessCondition/test/getAccessCondition.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.." 2 | import {ethers} from "ethers" 3 | 4 | 5 | describe("get AccessCondition", () => { 6 | let signer, signer2; 7 | jest.setTimeout(60000); 8 | 9 | beforeAll(async () => { 10 | // const provider = ethers.getDefaultProvider("mainnet"); 11 | signer = new ethers.Wallet( 12 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b", 13 | ); 14 | signer2 = new ethers.Wallet( 15 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8a", 16 | ); 17 | }); 18 | 19 | test("verify Access", async () => { 20 | const { data } = await _package.getAccessCondition( 21 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 22 | ); 23 | expect(data.aggregator).toBe("([2] and [1])"); 24 | expect(data.owner).toBe("0xeaf4e24ffc1a2f53c07839a74966a6611b8cb8a1"); 25 | }, 20000); 26 | 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /src/methods/getAuthMessage/index.ts: -------------------------------------------------------------------------------- 1 | import { API_NODE_HANDLER } from "../../util"; 2 | import { AuthMessage } from "../../types"; 3 | 4 | export const getAuthMessage = async (address: string): Promise => { 5 | try { 6 | const data = await API_NODE_HANDLER(`/api/message/${address}`, "GET"); 7 | return { message: data[0].message, error: null }; 8 | } catch (err: any) { 9 | return { message: null, error: JSON.parse(err?.message) }; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/methods/getAuthMessage/test/getAuthMessage.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../../"; 2 | 3 | describe("getAuthMessage", () => { 4 | test("Invalid Address", async () => { 5 | const { error } = await _package.getAuthMessage( 6 | "0x9a40b8EE3B8Fe7eB621cd142a651560Fa7" 7 | ); 8 | expect(typeof error).toBe("object"); 9 | expect(error?.message).toBe("Invalid address"); 10 | }, 20000); 11 | 12 | test("get message", async () => { 13 | const { message } = await _package.getAuthMessage( 14 | "0x9a40b8EE3B8Fe7eB621cd142a651560Fa7dF7CBa" 15 | ); 16 | 17 | expect(message).toMatch( 18 | /Please sign this message to prove you are owner of this account/i 19 | ); 20 | }, 20000); 21 | }); 22 | -------------------------------------------------------------------------------- /src/methods/getJWT/index.ts: -------------------------------------------------------------------------------- 1 | import { API_NODE_HANDLER } from "../../util"; 2 | 3 | export const getJWT = async (address: string, payload: string, useAsRefreshToken = false, chain = "ALL") => { 4 | try { 5 | const data = !useAsRefreshToken ? await API_NODE_HANDLER 6 | ( 7 | `/api/message/get-jwt`, "POST", "", 8 | { address, signature: payload, chain }, 9 | 10 | ) : await API_NODE_HANDLER 11 | ( 12 | `/api/message/get-jwt`, "PUT", "", 13 | { address, refreshToken: payload }, 14 | ); 15 | return { JWT: data.token, refreshToken: data.refreshToken, error: null }; 16 | } catch (err) { 17 | return { JWT: null, error: "Invalid Signature" }; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/methods/getJWT/test/getJWT.spec.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | import { getAuthMessage } from "../../getAuthMessage"; 3 | import { ethers } from "ethers"; 4 | 5 | describe("getJWT", () => { 6 | let signer; 7 | 8 | beforeAll(async () => { 9 | signer = new ethers.Wallet( 10 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b", 11 | ); 12 | }); 13 | 14 | 15 | test("Invalid Signature", async () => { 16 | const { error } = await _package.getJWT( 17 | signer.address, 18 | "signature", 19 | ); 20 | expect(error).toBe("Invalid Signature"); 21 | }, 20000); 22 | 23 | test("get jwt", async () => { 24 | const authMessage = await getAuthMessage(signer.address); 25 | const signedMessage = await signer.signMessage(authMessage.message); 26 | 27 | const { JWT, error, refreshToken } = await _package.getJWT( 28 | signer.address, 29 | signedMessage, 30 | ); 31 | expect(error).toBe(null); 32 | expect(JWT).toMatch(/jwt:/i); 33 | expect(refreshToken).toMatch(/r:/i); 34 | 35 | let secound_res = await _package.getJWT( 36 | signer.address, 37 | refreshToken, 38 | true 39 | ); 40 | expect(secound_res.JWT).toMatch(/jwt:/i); 41 | expect(secound_res.refreshToken).toMatch(/r:/i); 42 | }, 20000); 43 | }) -------------------------------------------------------------------------------- /src/methods/index.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "./generate" 2 | import { getJWT } from "./getJWT" 3 | import { getAuthMessage } from "./getAuthMessage" 4 | import { recoverKey } from "./recoverKey" 5 | import { recoverShards } from "./recoverShards" 6 | import { saveShards } from "./saveShards" 7 | import { shardKey } from "./shardKey" 8 | import { revokeAccess } from "./revokeAccess" 9 | import { accessControl } from "./accessControl" 10 | import { shareToAddress } from "./shareToAddress" 11 | import { transferOwnership } from "./transferOwnership" 12 | import { getAccessCondition } from "./getAccessCondition" 13 | 14 | export { 15 | generate, 16 | getJWT, 17 | getAuthMessage, 18 | recoverShards, 19 | revokeAccess, 20 | recoverKey, 21 | saveShards, 22 | shardKey, 23 | accessControl, 24 | shareToAddress, 25 | transferOwnership, 26 | getAccessCondition 27 | } 28 | 29 | export default { 30 | generate, 31 | getJWT, 32 | getAuthMessage, 33 | recoverShards, 34 | revokeAccess, 35 | recoverKey, 36 | saveShards, 37 | shardKey, 38 | accessControl, 39 | shareToAddress, 40 | transferOwnership, 41 | getAccessCondition 42 | } -------------------------------------------------------------------------------- /src/methods/recoverKey/index.ts: -------------------------------------------------------------------------------- 1 | import { KeyShard } from "../../types"; 2 | 3 | let bls: any = null; 4 | if (typeof window === "undefined") { 5 | bls = eval("require")("bls-eth-wasm"); 6 | } else { 7 | bls = require("bls-eth-wasm/browser"); 8 | } 9 | 10 | export const recoverKey = async (keyShards: KeyShard[]) => { 11 | if ( 12 | !Array.isArray(keyShards) || 13 | !(typeof keyShards[0].index == "string") || 14 | !(typeof keyShards[0].key == "string") 15 | ) { 16 | throw new Error( 17 | "keyShards must be an array of objects containing these keys [index, key]" 18 | ); 19 | } 20 | try { 21 | const idVec: any[] = []; 22 | const secVec: any[] = []; 23 | await bls.init(bls.BLS12_381).then(() => bls.getCurveOrder()); 24 | 25 | keyShards.map((keyShard) => { 26 | const sk = new bls.SecretKey(); 27 | //convert readable string into secretKey vectors 28 | sk.deserializeHexStr(keyShard.key); 29 | secVec.push(sk); 30 | 31 | //convert readable string into Id vectors 32 | const id = new bls.Id(); 33 | id.deserializeHexStr(keyShard.index); 34 | idVec.push(id); 35 | }); 36 | const sec = new bls.SecretKey(); 37 | 38 | //recover key 39 | sec.recover(secVec, idVec); 40 | const s = sec.serializeToHexStr(); 41 | return { masterKey: s, error: null }; 42 | } catch (err) { 43 | return { masterKey: null, error: "can't recover Key" }; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/methods/recoverKey/test/recoverKey.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | import { generate } from "../../generate"; 3 | 4 | describe("recover key", () => { 5 | test("generate and Recover Key", async () => { 6 | const { masterKey, keyShards } = await generate(); 7 | const { masterKey: msk, error } = await _package.recoverKey(keyShards); 8 | expect(msk).toEqual(masterKey); 9 | }, 20000); 10 | }); 11 | -------------------------------------------------------------------------------- /src/methods/recoverShards/index.ts: -------------------------------------------------------------------------------- 1 | import { API_NODE_HANDLER } from "../../util"; 2 | import { AuthToken } from "../../types"; 3 | import { RecoverShards } from "../../types"; 4 | 5 | function shuffleArray(array: number[]): number[] { 6 | for (let i = array.length - 1; i > 0; i--) { 7 | const j = Math.floor(Math.random() * (i + 1)); 8 | [array[i], array[j]] = [array[j], array[i]]; 9 | } 10 | return array; 11 | } 12 | 13 | function randSelect(k: number, n: number): number[] { 14 | if (k > n) { 15 | throw new Error("k cannot be greater than n"); 16 | } 17 | 18 | const numbers = Array.from({ length: n }, (_, i) => i + 1); 19 | const shuffledNumbers = shuffleArray(numbers); 20 | return shuffledNumbers.slice(0, k).sort((a, b) => a - b); 21 | } 22 | 23 | export const recoverShards = async ( 24 | address: string, 25 | cid: string, 26 | auth_token: AuthToken, 27 | numOfShards = 3, 28 | dynamicData = {} 29 | ): Promise => { 30 | try { 31 | const nodeIndexSelected = randSelect(numOfShards, 5); 32 | const nodeUrl = nodeIndexSelected.map( 33 | (elem) => `/api/retrieveSharedKey/${elem}` 34 | ); 35 | // send encryption key 36 | const requestData = async (url: any, index: any) => { 37 | try { 38 | const response = await API_NODE_HANDLER(url, "POST", auth_token, { 39 | address, 40 | cid, 41 | dynamicData, 42 | }); 43 | return response; 44 | } catch (error: any) { 45 | throw new Error(error?.message || "Unknown error"); 46 | } 47 | }; 48 | const recoveredShards = []; 49 | for (const [index, url] of nodeUrl.entries()) { 50 | const response = await requestData(url, index); 51 | 52 | await new Promise((resolve) => setTimeout(resolve, 1000)); 53 | recoveredShards.push(response?.payload); 54 | } 55 | return { 56 | shards: recoveredShards, 57 | error: null, 58 | }; 59 | } catch (err: any) { 60 | if (err.message.includes("null")) { 61 | return { 62 | shards: [], 63 | error: `cid not found`, 64 | }; 65 | } 66 | return { 67 | shards: [], 68 | error: JSON.parse(err?.message), 69 | }; 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /src/methods/recoverShards/test/recoverShards.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | import { getAuthMessage } from "../../getAuthMessage"; 3 | import { saveShards } from "../../saveShards"; 4 | import { ethers } from "ethers"; 5 | 6 | /** 7 | * @jest-environment jsdom 8 | */ 9 | describe("recoverShards:", () => { 10 | let signer; 11 | 12 | beforeAll(async () => { 13 | const provider = ethers.getDefaultProvider("mainnet"); 14 | signer = new ethers.Wallet( 15 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b", 16 | provider 17 | ); 18 | }); 19 | 20 | test("Invalid Signature", async () => { 21 | const { error } = await _package.recoverShards( 22 | signer.address, 23 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQA", 24 | "signature", 25 | 5 26 | ); 27 | expect(typeof error).toBe("object"); 28 | expect(error?.message).toBe("Invalid Signature"); 29 | }, 20000); 30 | 31 | test("save Key", async () => { 32 | const authMessage = await getAuthMessage(signer.address); 33 | const signedMessage = await signer.signMessage(authMessage.message); 34 | 35 | const { error, isSuccess } = await saveShards( 36 | signer.address, 37 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQA", 38 | signedMessage, 39 | [ 40 | { key: "1", index: "1" }, 41 | { key: "2", index: "2" }, 42 | { key: "3", index: "3" }, 43 | { key: "4", index: "4" }, 44 | { key: "5", index: "5" }, 45 | ] 46 | ); 47 | expect(isSuccess).toBe(true); 48 | expect(error).toBe(null); 49 | }, 20000); 50 | 51 | test("recover Key authorized", async () => { 52 | const authMessage = await getAuthMessage(signer.address); 53 | const signedMessage = await signer.signMessage(authMessage.message); 54 | 55 | const { error, shards } = await _package.recoverShards( 56 | signer.address, 57 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQA", 58 | signedMessage, 59 | 5 60 | ); 61 | 62 | expect(error).toBe(null); 63 | expect(shards).toEqual([ 64 | { index: "1", key: "1" }, 65 | { index: "2", key: "2" }, 66 | { index: "3", key: "3" }, 67 | { index: "4", key: "4" }, 68 | { index: "5", key: "5" }, 69 | ]); 70 | }, 20000); 71 | 72 | test("missing Cid", async () => { 73 | const authMessage = await getAuthMessage(signer.address); 74 | const signedMessage = await signer.signMessage(authMessage.message); 75 | 76 | const { error, shards } = await _package.recoverShards( 77 | signer.address, 78 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQZ", 79 | signedMessage 80 | ); 81 | 82 | expect(error).toMatch(/cid not found/i); 83 | expect(shards).toEqual([]); 84 | }, 20000); 85 | 86 | test("recover Key Unauthorized", async () => { 87 | const signer2 = new ethers.Wallet( 88 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8f" 89 | ); 90 | const authMessage = await getAuthMessage(signer2.address); 91 | const signedMessage2 = await signer2.signMessage(authMessage.message ?? ""); 92 | 93 | const { error, shards } = await _package.recoverShards( 94 | signer2.address, 95 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQA", 96 | signedMessage2, 97 | 5 98 | ); 99 | expect(shards).toEqual([]); 100 | expect(error?.message?.message?.message).toBe("Access Denied"); 101 | }, 20000); 102 | }); 103 | -------------------------------------------------------------------------------- /src/methods/revokeAccess/index.ts: -------------------------------------------------------------------------------- 1 | import { API_NODE_HANDLER } from "../../util"; 2 | import { AuthToken, LightHouseSDKResponse } from "../../types"; 3 | import { isCidReg, isEqual } from "../../util"; 4 | 5 | export const revokeAccess = async ( 6 | address: string, 7 | cid: string, 8 | auth_token: AuthToken, 9 | revokeTo: Array 10 | ): Promise => { 11 | if (!isCidReg(cid)) { 12 | return { 13 | isSuccess: false, 14 | error: "Invalid CID", 15 | }; 16 | } 17 | try { 18 | const nodeId = [1, 2, 3, 4, 5]; 19 | const nodeUrl = nodeId.map((elem) => `/api/setSharedKey/${elem}`); 20 | 21 | // send encryption key 22 | const requestData = async (url: any) => { 23 | try { 24 | const response = await API_NODE_HANDLER(url, "DELETE", auth_token, { 25 | address, 26 | cid, 27 | revokeTo, 28 | }); 29 | return response; 30 | } catch (error: any) { 31 | return { 32 | error, 33 | }; 34 | } 35 | }; 36 | const data = []; 37 | for (const [index, url] of nodeUrl.entries()) { 38 | const response = await requestData(url); 39 | if (response.error) { 40 | return { 41 | isSuccess: false, 42 | error: JSON.parse(response?.error?.message), 43 | }; 44 | } 45 | await new Promise((resolve) => setTimeout(resolve, 1000)); 46 | data.push(response); 47 | } 48 | const temp = data.map((elem, index) => ({ ...elem, data: null })); 49 | return { 50 | isSuccess: isEqual(...temp), 51 | error: null, 52 | }; 53 | } catch (err: any) { 54 | return { 55 | isSuccess: false, 56 | error: err, 57 | }; 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /src/methods/revokeAccess/test/revoke.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | import { ethers } from "ethers"; 3 | import { getAuthMessage } from "../../getAuthMessage"; 4 | 5 | describe("revoke file address", () => { 6 | let signer, signer2; 7 | 8 | beforeAll(async () => { 9 | signer = new ethers.Wallet( 10 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 11 | ); 12 | signer2 = new ethers.Wallet( 13 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8a" 14 | ); 15 | }); 16 | 17 | test("revoke to address", async () => { 18 | const authMessage = await getAuthMessage(signer.address); 19 | const signedMessage = await signer.signMessage(authMessage.message); 20 | const { error, isSuccess } = await _package.revokeAccess( 21 | signer.address, 22 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 23 | signedMessage, 24 | [signer2.address] 25 | ); 26 | expect(error).toBe(null); 27 | expect(isSuccess).toBe(true); 28 | }, 20000); 29 | 30 | test("invalid Cid", async () => { 31 | const authMessage = await getAuthMessage(signer.address); 32 | const signedMessage = await signer.signMessage(authMessage.message); 33 | 34 | const { error, isSuccess } = await _package.revokeAccess( 35 | signer.address, 36 | "cid", 37 | signedMessage, 38 | [signer2.address] 39 | ); 40 | expect(isSuccess).toBe(false); 41 | expect(error).toMatch(/invalid Cid/i); 42 | }, 20000); 43 | 44 | test("invalid Access", async () => { 45 | const authMessage = await getAuthMessage(signer2.address); 46 | const signedMessage = await signer2.signMessage(authMessage.message); 47 | 48 | const { error, isSuccess } = await _package.revokeAccess( 49 | signer2.address, 50 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 51 | signedMessage, 52 | [signer2.address] 53 | ); 54 | expect(isSuccess).toBe(false); 55 | expect(error.message.message.message).toMatch(/Access Denied/i); 56 | }, 20000); 57 | }); 58 | -------------------------------------------------------------------------------- /src/methods/saveShards/index.ts: -------------------------------------------------------------------------------- 1 | import { AuthToken, KeyShard } from "../../types"; 2 | import { API_NODE_HANDLER } from "../../util"; 3 | import { isEqual, isCidReg } from "../../util"; 4 | 5 | export const saveShards = async ( 6 | address: string, 7 | cid: string, 8 | auth_token: AuthToken, 9 | keyShards: KeyShard[], 10 | shareTo: string[] = [] 11 | ) => { 12 | if (!isCidReg(cid)) { 13 | return { 14 | isSuccess: false, 15 | error: "Invalid CID", 16 | }; 17 | } 18 | if (!Array.isArray(keyShards) || keyShards.length != 5) { 19 | return { 20 | isSuccess: false, 21 | error: "keyShards must be an array of 5 objects", 22 | }; 23 | } 24 | try { 25 | const nodeId = [1, 2, 3, 4, 5]; 26 | const nodeUrl = nodeId.map((elem) => `/api/setSharedKey/${elem}`); 27 | const requestData = async (url: any, index: any) => { 28 | try { 29 | const response = await API_NODE_HANDLER( 30 | url, 31 | "POST", 32 | auth_token, 33 | shareTo.length > 0 34 | ? { 35 | address, 36 | cid, 37 | payload: keyShards[index], 38 | sharedTo: shareTo, 39 | } 40 | : { 41 | address, 42 | cid, 43 | payload: keyShards[index], 44 | } 45 | ); 46 | return response; 47 | } catch (error: any) { 48 | return { 49 | error, 50 | }; 51 | } 52 | }; 53 | const data = []; 54 | for (const [index, url] of nodeUrl.entries()) { 55 | const response = await requestData(url, index); 56 | if (response.error) { 57 | return { 58 | isSuccess: false, 59 | error: JSON.parse(response?.error?.message), 60 | }; 61 | } 62 | await new Promise((resolve) => setTimeout(resolve, 1000)); 63 | data.push(response); 64 | } 65 | const temp = data.map((elem, index) => ({ ...elem, data: null })); 66 | return { 67 | isSuccess: isEqual(...temp) && data[0]?.message === "success", 68 | error: null, 69 | }; 70 | } catch (err: any) { 71 | return { 72 | isSuccess: false, 73 | error: err, 74 | }; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /src/methods/saveShards/test/saveShards.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.." 2 | import { getAuthMessage } from "../../getAuthMessage" 3 | import { ethers } from "ethers" 4 | 5 | describe("saveShards", () => { 6 | let signer; 7 | 8 | beforeAll(async () => { 9 | const provider = ethers.getDefaultProvider("mainnet"); 10 | signer = new ethers.Wallet( 11 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b", 12 | provider 13 | ); 14 | }); 15 | 16 | test("Invalid Signature", async () => { 17 | const { error } = await _package.saveShards( 18 | signer.address, 19 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 20 | "signature", 21 | [ 22 | { key: "1", index: "1" }, 23 | { key: "2", index: "2" }, 24 | { key: "3", index: "3" }, 25 | { key: "4", index: "4" }, 26 | { key: "5", index: "5" }, 27 | ] 28 | ); 29 | expect(typeof error).toBe("object"); 30 | expect(error?.message).toBe("Invalid Signature"); 31 | }, 20000); 32 | 33 | test("save Key", async () => { 34 | const authMessage = await getAuthMessage(signer.address); 35 | const signedMessage = await signer.signMessage(authMessage.message); 36 | 37 | const { error, isSuccess } = await _package.saveShards( 38 | signer.address, 39 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 40 | signedMessage, 41 | [ 42 | { key: "1", index: "1" }, 43 | { key: "2", index: "2" }, 44 | { key: "3", index: "3" }, 45 | { key: "4", index: "4" }, 46 | { key: "5", index: "5" }, 47 | ] 48 | ); 49 | expect(isSuccess).toBe(true); 50 | expect(error).toBe(null); 51 | }, 20000); 52 | 53 | test("save Key", async () => { 54 | const authMessage = await getAuthMessage(signer.address); 55 | const signedMessage = await signer.signMessage(authMessage.message); 56 | 57 | const { error, isSuccess } = await _package.saveShards( 58 | signer.address, 59 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQJ", 60 | signedMessage, 61 | [ 62 | { key: "1", index: "1" }, 63 | { key: "2", index: "2" }, 64 | { key: "3", index: "3" }, 65 | ] 66 | ); 67 | expect(isSuccess).toBe(false); 68 | expect(error).toMatch(/keyShards must be an array of 5 objects/i); 69 | }, 20000); 70 | 71 | test("invalid Cid", async () => { 72 | const authMessage = await getAuthMessage(signer.address); 73 | const signedMessage = await signer.signMessage(authMessage.message); 74 | 75 | const { error, isSuccess } = await _package.saveShards( 76 | signer.address, 77 | "cid", 78 | signedMessage, 79 | [ 80 | { key: "1", index: "1" }, 81 | { key: "2", index: "2" }, 82 | { key: "3", index: "3" }, 83 | { key: "4", index: "4" }, 84 | { key: "5", index: "5" }, 85 | ] 86 | ); 87 | expect(isSuccess).toBe(false); 88 | expect(error).toMatch(/invalid Cid/i); 89 | }, 20000); 90 | }); 91 | -------------------------------------------------------------------------------- /src/methods/shardKey/index.ts: -------------------------------------------------------------------------------- 1 | let bls: any = null; 2 | if (typeof window === "undefined") { 3 | bls = eval("require")("bls-eth-wasm"); 4 | } else { 5 | bls = require("bls-eth-wasm/browser"); 6 | } 7 | 8 | export const shardKey = async (key: string, threshold = 3, keyCount = 5) => { 9 | try { 10 | const msk = []; 11 | const idVec: any[] = []; 12 | const secVec = []; 13 | await bls.init(bls.BLS12_381).then(() => bls.getCurveOrder()); 14 | const masterKey = new bls.SecretKey(); 15 | masterKey.deserializeHexStr(key); 16 | msk.push(masterKey); 17 | 18 | for (let i = 0; i < threshold; i++) { 19 | const sk = new bls.SecretKey(); 20 | sk.setByCSPRNG(); 21 | msk.push(sk); 22 | } 23 | 24 | /* 25 | key sharing 26 | */ 27 | for (let i = 0; i < keyCount; i++) { 28 | //create random Vector ID(points on the ECC) 29 | const id = new bls.Id(); 30 | id.setByCSPRNG(); 31 | idVec.push(id); 32 | 33 | //Create a secKey Shard 34 | const sk = new bls.SecretKey(); 35 | sk.share(msk, idVec[i]); 36 | secVec.push(sk); 37 | } 38 | 39 | //Convert vector in to readable hex values 40 | return { 41 | isShardable: true, 42 | keyShards: 43 | secVec?.map((sk, index) => ({ 44 | key: sk.serializeToHexStr(), 45 | index: idVec[index].serializeToHexStr(), 46 | })) ?? [], 47 | }; 48 | } catch (e) { 49 | return { isShardable: false, keyShards: [] }; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /src/methods/shardKey/test/shardKey.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | 3 | describe("isKeyShadable", () => { 4 | test("check if key is shadable: False", async () => { 5 | const { isShardable } = await _package.shardKey( 6 | "0b2acf87909bd1215858e5c0ec99359cadfcf918a4fac539ec28bec80ae" 7 | ); 8 | expect(isShardable).toBe(false); 9 | }, 20000); 10 | test("check if key is shadable: true", async () => { 11 | const { isShardable, keyShards } = await _package.shardKey( 12 | "0b2ccf87909bd1215858e5c0ec99359cadfcf918a4fac53e3e88e9ec28bec678" 13 | ); 14 | expect(isShardable).toBe(true); 15 | expect(keyShards.length).toBe(5); 16 | }, 20000); 17 | 18 | test("test MasterKey Recovery: 4/5 part", async () => { 19 | const knownKey = 20 | "0a16088df55283663f7fea6c5f315bde968024b7ac5d715af0325a5507700e5e"; 21 | const { isShardable, keyShards } = await _package.shardKey(knownKey, 3, 5); 22 | //recover keys from shards 23 | // console.log(keyShards, isShardable, knownKey); 24 | const { masterKey } = await _package.recoverKey([ 25 | keyShards[2], 26 | keyShards[0], 27 | keyShards[1], 28 | keyShards[4], 29 | ]); 30 | expect(masterKey).toBe(knownKey); 31 | }); 32 | test("test MasterKey Recovery: 5/5 part", async () => { 33 | const knownKey = 34 | "0a16088df55283663f7fea6c5f315bde968024b7ac5d715af0325a5507700e5e"; 35 | const { isShardable, keyShards } = await _package.shardKey(knownKey, 3, 5); 36 | //recover keys from shards 37 | // console.log(keyShards, isShardable, knownKey); 38 | const { masterKey } = await _package.recoverKey([ 39 | keyShards[2], 40 | keyShards[0], 41 | keyShards[1], 42 | keyShards[4], 43 | keyShards[3], 44 | ]); 45 | expect(masterKey).toBe(knownKey); 46 | }); 47 | // test("test MasterKey Recovery: 3/5 part", async () => { 48 | // const knownKey = 49 | // "0a16088df55283663f7fea6c5f315bde968024b7ac5d715af0325a5507700e5e"; 50 | // const { isShardable, keyShards } = await _package.shardKey(knownKey, 3, 5); 51 | // //recover keys from shards 52 | // console.log(keyShards, isShardable, knownKey); 53 | // const { masterKey } = await _package.recoverKey([ 54 | // keyShards[2], 55 | // keyShards[3], 56 | // keyShards[4], 57 | // ]); 58 | // expect(masterKey).toBe(knownKey); 59 | // }); 60 | test("test MasterKey Recovery: 2/5 part", async () => { 61 | const knownKey = 62 | "0a16088df55283663f7fea6c5f315bde968024b7ac5d715af0325a5507700e5e"; 63 | const { isShardable, keyShards } = await _package.shardKey(knownKey, 3, 5); 64 | //recover keys from shards 65 | // console.log(keyShards, isShardable, knownKey); 66 | const { masterKey } = await _package.recoverKey([ 67 | keyShards[0], 68 | keyShards[1], 69 | ]); 70 | expect(masterKey).not.toBe(knownKey); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /src/methods/shareToAddress/index.ts: -------------------------------------------------------------------------------- 1 | import { API_NODE_HANDLER } from "../../util"; 2 | import { AuthToken, LightHouseSDKResponse } from "../../types"; 3 | import { isEqual, isCidReg } from "../../util/index"; 4 | 5 | export const shareToAddress = async ( 6 | address: string, 7 | cid: string, 8 | auth_token: AuthToken, 9 | shareTo: Array 10 | ): Promise => { 11 | if (!isCidReg(cid)) { 12 | return { 13 | isSuccess: false, 14 | error: "Invalid CID", 15 | }; 16 | } 17 | try { 18 | const nodeId = [1, 2, 3, 4, 5]; 19 | const nodeUrl = nodeId.map((elem) => `/api/setSharedKey/${elem}`); 20 | // send encryption key 21 | const requestData = async (url: any) => { 22 | try { 23 | const response = await API_NODE_HANDLER(url, "PUT", auth_token, { 24 | address, 25 | cid: cid, 26 | shareTo, 27 | }); 28 | return response; 29 | } catch (error: any) { 30 | return { 31 | error, 32 | }; 33 | } 34 | }; 35 | const data = []; 36 | for (const [index, url] of nodeUrl.entries()) { 37 | const response = await requestData(url); 38 | if (response.error) { 39 | return { 40 | isSuccess: false, 41 | error: JSON.parse(response?.error?.message), 42 | }; 43 | } 44 | await new Promise((resolve) => setTimeout(resolve, 1000)); 45 | data.push(response); 46 | } 47 | const temp = data.map((elem, index) => ({ ...elem, data: null })); 48 | return { 49 | isSuccess: isEqual(...temp) && temp[0]?.message === "success", 50 | error: null, 51 | }; 52 | } catch (err: any) { 53 | return { 54 | isSuccess: false, 55 | error: err, 56 | }; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/methods/shareToAddress/test/sharedToAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | import { ethers } from "ethers"; 3 | import { getAuthMessage } from "../../getAuthMessage"; 4 | 5 | describe("shareFile To address", () => { 6 | let signer, signer2; 7 | 8 | beforeAll(async () => { 9 | const provider = ethers.getDefaultProvider("mainnet"); 10 | signer = new ethers.Wallet( 11 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b", 12 | provider 13 | ); 14 | 15 | signer2 = new ethers.Wallet( 16 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8a", 17 | provider 18 | ); 19 | }); 20 | 21 | test("Invalid access", async () => { 22 | const authMessage = await getAuthMessage(signer2.address); 23 | const signedMessage = await signer2.signMessage(authMessage.message); 24 | const { isSuccess, error } = await _package.shareToAddress( 25 | signer2.address, 26 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 27 | signedMessage, 28 | ["0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1"] 29 | ); 30 | expect(typeof error.message).toBe("string"); 31 | expect(error.message).toMatch(/Denied/); 32 | }, 20000); 33 | 34 | test("share to address", async () => { 35 | const authMessage = await getAuthMessage(signer.address); 36 | const signedMessage = await signer.signMessage(authMessage.message); 37 | const { error, isSuccess } = await _package.shareToAddress( 38 | signer.address, 39 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 40 | signedMessage, 41 | ["0xF0Bc72fA04aea04d04b1fA80B359Adb566E1c8B1"] 42 | ); 43 | expect(error).toBe(null); 44 | expect(isSuccess).toBe(true); 45 | }, 20000); 46 | }); 47 | -------------------------------------------------------------------------------- /src/methods/transferOwnership/index.ts: -------------------------------------------------------------------------------- 1 | import { API_NODE_HANDLER } from "../../util"; 2 | import { AuthToken, LightHouseSDKResponse } from "../../types"; 3 | import { isCidReg, isEqual } from "../../util"; 4 | 5 | export const transferOwnership = async ( 6 | address: string, 7 | cid: string, 8 | newOwner: string, 9 | auth_token: AuthToken, 10 | resetSharedTo = true 11 | ): Promise => { 12 | if (!isCidReg(cid)) { 13 | return { 14 | isSuccess: false, 15 | error: "Invalid CID", 16 | }; 17 | } 18 | try { 19 | const nodeIndexSelected = [1, 2, 3, 4, 5]; 20 | const nodeUrl = nodeIndexSelected.map( 21 | (elem) => `/api/transferOwnership/${elem}` 22 | ); 23 | // send encryption key 24 | const requestData = async (url: any) => { 25 | try { 26 | const response = await API_NODE_HANDLER(url, "POST", auth_token, { 27 | address, 28 | cid, 29 | newOwner, 30 | resetSharedTo, 31 | }); 32 | return response; 33 | } catch (error: any) { 34 | return { 35 | error, 36 | }; 37 | } 38 | }; 39 | const data = []; 40 | for (const [index, url] of nodeUrl.entries()) { 41 | const response = await requestData(url); 42 | if (response.error) { 43 | return { 44 | isSuccess: false, 45 | error: JSON.parse(response?.error?.message), 46 | }; 47 | } 48 | await new Promise((resolve) => setTimeout(resolve, 1000)); 49 | data.push(response); 50 | } 51 | return { 52 | isSuccess: isEqual(...data) && data[0]?.message === "success", 53 | error: null, 54 | }; 55 | } catch (err: any) { 56 | return { 57 | isSuccess: false, 58 | error: err, 59 | }; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/methods/transferOwnership/test/transferOwnership.test.ts: -------------------------------------------------------------------------------- 1 | import _package from "../.."; 2 | import { ethers } from "ethers"; 3 | 4 | describe("Transfer OwnerShip", () => { 5 | let signer, signer2; 6 | 7 | beforeAll(async () => { 8 | signer = new ethers.Wallet( 9 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8b" 10 | ); 11 | signer2 = new ethers.Wallet( 12 | "0x8218aa5dbf4dbec243142286b93e26af521b3e91219583595a06a7765abc9c8a" 13 | ); 14 | }); 15 | 16 | test("transfer Ownership to address", async () => { 17 | let authMessage = await _package.getAuthMessage(signer.address); 18 | let signedMessage = await signer.signMessage(authMessage.message); 19 | const { error, isSuccess } = await _package.transferOwnership( 20 | signer.address, 21 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 22 | signer2.address, 23 | signedMessage 24 | ); 25 | authMessage = await _package.getAuthMessage(signer2.address); 26 | signedMessage = await signer2.signMessage(authMessage.message); 27 | const { error: _error, isSuccess: _isSuccess } = 28 | await _package.transferOwnership( 29 | signer2.address, 30 | "QmbFMke1KXqnYyBBWxB74N4c5SBnJMVAiMNRcGu6x1AwQH", 31 | signer.address, 32 | signedMessage 33 | ); 34 | // expect(error).toBe(null); 35 | // expect(isSuccess).toBe(true); 36 | expect(_error).toBe(null); 37 | expect(_isSuccess).toBe(true); 38 | }, 80000); 39 | }); 40 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { Signature } from "bls-eth-wasm"; 2 | 3 | // Key Shard Interface 4 | export interface KeyShard { 5 | key: string; 6 | index: string; 7 | } 8 | 9 | // Error Value Type 10 | export type ErrorValue = string | string[] | number | boolean | null | object | any; 11 | 12 | // Generated Key Interface 13 | export interface GeneratedKey { 14 | masterKey: string | null; 15 | keyShards: Array; 16 | } 17 | 18 | // Generate Input Interface 19 | export interface GenerateInput { 20 | threshold?: number; 21 | keyCount?: number; 22 | } 23 | 24 | // Auth Message Interface 25 | export interface AuthMessage { 26 | message: string | null; 27 | error: string | ErrorValue | null; 28 | } 29 | 30 | // Recovered Key Interface 31 | export interface RecoveredKey { 32 | masterKey: string | null; 33 | error: string | ErrorValue | null; 34 | } 35 | 36 | // Recover Shards Interface 37 | export interface RecoverShards { 38 | shards: KeyShard[]; 39 | error: ErrorValue; 40 | } 41 | 42 | // WithPrefix Type 43 | type WithPrefix = `${T}${string}`; 44 | 45 | export type SignedMessage = string; 46 | export type JWT = WithPrefix<'jwt:'>; 47 | 48 | export type AuthToken = SignedMessage | JWT 49 | // LightHouse SDK Response Interface 50 | export interface LightHouseSDKResponse { 51 | isSuccess: boolean; 52 | error: ErrorValue; 53 | } 54 | 55 | // ChainType and DecryptionType Types 56 | export type ChainType = "EVM" | "evm" | "solana" | "SOLANA"; 57 | export type DecryptionType = "ADDRESS" | "ACCESS_CONDITIONS"; 58 | 59 | export interface ReturnValueTest { 60 | comparator: '==' | '>=' | '<=' | '!=' | '>' | '<'; 61 | value: number | string | Array; 62 | } 63 | 64 | export interface EVMCondition { 65 | id: number; 66 | standardContractType: 'ERC20' | 'ERC721' | 'ERC1155' | 'Custom' | ''; 67 | contractAddress?: string; 68 | chain: string; 69 | method: string; 70 | parameters?: Array; 71 | returnValueTest: ReturnValueTest; 72 | inputArrayType?: Array; 73 | outputType?: string; 74 | } 75 | 76 | export interface SolanaCondition { 77 | id: number; 78 | contractAddress?: string; 79 | chain: string; 80 | method: string; 81 | standardContractType: 'spl-token' | ''; 82 | parameters?: Array; 83 | pdaInterface: { 84 | offset?: number; 85 | selector?: string; 86 | }; 87 | returnValueTest: ReturnValueTest; 88 | } 89 | 90 | export type Condition = EVMCondition | SolanaCondition; 91 | 92 | export interface UpdateConditionSchema { 93 | chainType: 'EVM' | 'SOLANA'; 94 | conditions: Condition[]; 95 | decryptionType: 'ADDRESS' | 'ACCESS_CONDITIONS'; 96 | address: string; 97 | cid: string; 98 | aggregator?: string; 99 | } 100 | 101 | export interface AccessConditionSchema extends UpdateConditionSchema { 102 | keyShards: Array; // Define the structure of key shards if available 103 | } 104 | 105 | 106 | // Get Access Condition Interface 107 | export interface IGetAccessCondition { 108 | aggregator: string; 109 | conditions?: Condition[]; 110 | conditionsSolana: any[]; // Replace 'any' with a specific type if needed. 111 | sharedTo: any[]; // Replace 'any' with a specific type if needed. 112 | owner: string; 113 | cid: string; 114 | } 115 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | import config from "../config"; 2 | import { json } from "stream/consumers"; 3 | 4 | export const isCidReg = (cid: string) => 5 | /Qm[1-9A-HJ-NP-Za-km-z]{44}|b[A-Za-z2-7]{58}|B[A-Z2-7]{58}|z[1-9A-HJ-NP-Za-km-z]{48}|F[0-9A-F]{50}/.test( 6 | cid 7 | ); 8 | 9 | export const isEqual = (...objects: any) => 10 | objects.every( 11 | (obj: any) => JSON.stringify(obj) === JSON.stringify(objects[0]) 12 | ); 13 | 14 | export async function API_NODE_HANDLER( 15 | endpoint: string, 16 | verb: "GET" | "POST" | "DELETE" | "PUT", 17 | auth_token: string = "", 18 | body: any = null, 19 | retryCount = 3 20 | ) { 21 | const url = new URL( 22 | `${ 23 | config.isDev ? config.lighthouseBLSNodeDev : config.lighthouseAuthNode 24 | }${endpoint}` 25 | ); 26 | 27 | const init: any = { 28 | method: verb, 29 | headers: { 30 | "Content-type": "application/json", 31 | Authorization: auth_token ? `Bearer ${auth_token}` : "", 32 | }, 33 | }; 34 | 35 | if ((verb === "POST" || verb === "PUT" || verb === "DELETE") && body) { 36 | init.body = JSON.stringify(body); 37 | } 38 | for (let i = 0; i < retryCount; i++) { 39 | try { 40 | const response = await fetch(url.toString(), init); 41 | 42 | if (!response.ok) { 43 | if (response.status == 404) { 44 | throw new Error( 45 | JSON.stringify({ 46 | message: "fetch Error", 47 | statusCode: response.status, 48 | }) 49 | ); 50 | } 51 | const errorBody = await response.json(); 52 | throw new Error( 53 | JSON.stringify({ ...errorBody, statusCode: response.status }) 54 | ); 55 | } 56 | 57 | return response.json(); 58 | } catch (error: any) { 59 | if (!error?.message?.includes("fetch")) { 60 | throw error; 61 | } 62 | if (i === retryCount - 1) throw error; // Throw error on the last attempt 63 | await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait for 1 second 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es2022", 5 | "lib": [ 6 | "es2019", 7 | "dom" 8 | ], 9 | "module": "CommonJS", 10 | "sourceMap": false, 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "skipLibCheck": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "allowJs": true, 18 | "declaration": true, 19 | "resolveJsonModule": true, 20 | "types": [ 21 | "node" // Consider conditionally including this only for Node.js builds 22 | ], 23 | "rootDir": "./src", // Define root directory 24 | }, 25 | "include": [ 26 | "src" 27 | ], 28 | "exclude": [ 29 | "node_modules", 30 | "**/*.spec.ts", 31 | "**/*.test.ts", 32 | ".vscode" 33 | ] 34 | } --------------------------------------------------------------------------------