mirror of
https://github.com/pezkuwichain/pezkuwi-common.git
synced 2026-04-21 23:48:05 +00:00
Initial rebrand: @polkadot -> @pezkuwi (14 packages)
- Package namespace: @polkadot/* -> @pezkuwi/* - Repository: polkadot-js/common -> pezkuwichain/pezkuwi-common - Author: Pezkuwi Team <team@pezkuwichain.io> Core packages: - @pezkuwi/util (utilities) - @pezkuwi/util-crypto (crypto primitives) - @pezkuwi/keyring (account management) - @pezkuwi/networks (chain metadata) - @pezkuwi/hw-ledger (Ledger hardware wallet) - @pezkuwi/x-* (10 polyfill packages) Total: 14 packages Upstream: polkadot-js/common v14.0.1
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
root = true
|
||||
[*]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
tab_width=2
|
||||
end_of_line=lf
|
||||
charset=utf-8
|
||||
trim_trailing_whitespace=true
|
||||
max_line_length=120
|
||||
insert_final_newline=true
|
||||
@@ -0,0 +1,86 @@
|
||||
<!--
|
||||
|
||||
For general support, howto, coding and bundling questions, please
|
||||
use the Substrate & Pezkuwi StackExchange at
|
||||
|
||||
https://substrate.stackexchange.com/
|
||||
|
||||
and get other ecosystem developers involved. This issues in this
|
||||
repository are meant for the tracking of feature requests and bug
|
||||
reports.
|
||||
|
||||
While all issues are looked at non-bug and non-features would take
|
||||
quite a bit longer to get to and may yield less than satisfactory
|
||||
responses in this format.
|
||||
|
||||
Additionally, please ensure you have done a search on the existing
|
||||
and closed issues before logging a new request. This saves time on
|
||||
all sides.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
* **I'm submitting a ...**
|
||||
|
||||
<!---
|
||||
|
||||
REQUIRED:
|
||||
|
||||
Classify the type of report your are submitting
|
||||
|
||||
-->
|
||||
|
||||
- [ ] Bug report
|
||||
- [ ] Feature request
|
||||
- [ ] Support request
|
||||
- [ ] Other
|
||||
|
||||
|
||||
* **What is the current behavior and expected behavior?**
|
||||
|
||||
<!---
|
||||
|
||||
REQUIRED:
|
||||
|
||||
If you're describing a bug, tell us what should happen. If you're
|
||||
suggesting a change/improvement, tell us how it should work.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
* **What is the motivation for changing the behavior?**
|
||||
|
||||
<!---
|
||||
|
||||
OPTIONAL:
|
||||
|
||||
Suggest a motivation for the request or ideas how to implement the
|
||||
addition or change
|
||||
|
||||
-->
|
||||
|
||||
|
||||
* **Please tell us about your environment:**
|
||||
|
||||
<!---
|
||||
|
||||
REQUIRED:
|
||||
|
||||
Include as many relevant details about the environment in which you
|
||||
experienced the issue. Also ensure that you have tested against the
|
||||
latest stable releases if you believe this to be a bug
|
||||
|
||||
-->
|
||||
|
||||
- Version:
|
||||
- Environment:
|
||||
|
||||
- [ ] Node.js
|
||||
- [ ] Browser
|
||||
- [ ] Other (limited support for other environments)
|
||||
|
||||
- Language:
|
||||
|
||||
- [ ] JavaScript
|
||||
- [ ] TypeScript (include tsc --version)
|
||||
- [ ] Other
|
||||
@@ -0,0 +1,16 @@
|
||||
name: bot
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
approve:
|
||||
if: "! startsWith(github.event.head_commit.message, '[CI Skip]') && (!github.event.pull_request || github.event.pull_request.head.repo.full_name == github.repository)"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jacogr/action-approve@795afd1dd096a2071d7ec98740661af4e853b7da
|
||||
with:
|
||||
authors: jacogr, TarikGul
|
||||
labels: -auto
|
||||
token: ${{ secrets.GH_PAT_BOT }}
|
||||
@@ -0,0 +1,16 @@
|
||||
name: bot
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: jacogr/action-merge@d2d64b4545acd93b0a9575177d3d215ae3f92029
|
||||
with:
|
||||
checks: pr (build),pr (lint),pr (test)
|
||||
labels: -auto
|
||||
strategy: squash
|
||||
token: ${{ secrets.GH_PAT_BOT }}
|
||||
@@ -0,0 +1,25 @@
|
||||
name: 'Lock Threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '15 1/3 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
YARN_ENABLE_SCRIPTS: false
|
||||
steps:
|
||||
- uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836
|
||||
with:
|
||||
github-token: ${{ secrets.GH_PAT_BOT }}
|
||||
issue-inactive-days: '7'
|
||||
issue-comment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue
|
||||
if you think you have a related problem or query.
|
||||
pr-inactive-days: '2'
|
||||
pr-comment: >
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
@@ -0,0 +1,28 @@
|
||||
name: PR
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
pr:
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
step: ['lint', 'test', 'build', 'deno']
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
YARN_ENABLE_SCRIPTS: false
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
- uses: denoland/setup-deno@v1
|
||||
with:
|
||||
# Deno v2 throws errors with specific types therefore we set it to the last version before v2
|
||||
deno-version: v1.46.3
|
||||
- name: ${{ matrix.step }}
|
||||
if: always()
|
||||
run: |
|
||||
deno --version
|
||||
yarn install --immutable
|
||||
yarn ${{ matrix.step }}
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Master
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
master:
|
||||
if: "! startsWith(github.event.head_commit.message, '[CI Skip]')"
|
||||
strategy:
|
||||
matrix:
|
||||
step: ['build:release']
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
YARN_ENABLE_SCRIPTS: false
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
|
||||
GH_PAT: ${{ secrets.GH_PAT_BOT }}
|
||||
GH_RELEASE_GITHUB_API_TOKEN: ${{ secrets.GH_PAT_BOT }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GH_PAT_BOT }}
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 'lts/*'
|
||||
- name: ${{ matrix.step }}
|
||||
run: |
|
||||
yarn install --immutable
|
||||
yarn ${{ matrix.step }}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
--test-*
|
||||
_book/
|
||||
build/
|
||||
build-*/
|
||||
coverage/
|
||||
node_modules/
|
||||
tmp/
|
||||
/import_map.json
|
||||
/mod.ts
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.idea
|
||||
.npmrc
|
||||
.vscode
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
.pnp.*
|
||||
cc-test-reporter
|
||||
lerna-debug.log*
|
||||
npm-debug.log*
|
||||
package-lock.json
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
tsconfig.*buildinfo
|
||||
@@ -0,0 +1,3 @@
|
||||
Jaco <jacogr@gmail.com>
|
||||
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> <action@github.com>
|
||||
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Github Actions <action@github.com>
|
||||
@@ -0,0 +1,3 @@
|
||||
build
|
||||
coverage
|
||||
packages
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
module.exports = require('@polkadot/dev/config/prettier.cjs');
|
||||
Vendored
+948
File diff suppressed because one or more lines are too long
+15
@@ -0,0 +1,15 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
enableImmutableInstalls: false
|
||||
|
||||
enableProgressBars: false
|
||||
|
||||
logFilters:
|
||||
- code: YN0013
|
||||
level: discard
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||
+2036
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
# Contributing
|
||||
|
||||
## What?
|
||||
|
||||
Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit.
|
||||
A project is more like an open wiki than a standard guarded open source project.
|
||||
|
||||
## Rules
|
||||
|
||||
There are a few basic ground-rules for contributors (including the maintainer(s) of the project):
|
||||
|
||||
1. **No `--force` pushes** or modifying the Git history in any way. If you need to rebase, ensure you do it in your own repo.
|
||||
2. **Non-master branches**, prefixed with a short name moniker (e.g. `<initials>-<feature>`) must be used for ongoing work.
|
||||
3. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.
|
||||
4. A pull-request *must not be merged until CI* has finished successfully.
|
||||
|
||||
#### Merging pull requests once CI is successful:
|
||||
- A pull request with no large change to logic that is an urgent fix may be merged after a non-author contributor has reviewed it well.
|
||||
- No PR should be merged until all reviews' comments are addressed.
|
||||
|
||||
#### Reviewing pull requests:
|
||||
When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in:
|
||||
|
||||
- Buggy behaviour.
|
||||
- Undue maintenance burden.
|
||||
- Breaking with house coding style.
|
||||
- Pessimisation (i.e. reduction of speed as measured in the projects benchmarks).
|
||||
- Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on).
|
||||
- Uselessness (i.e. it does not strictly add a feature or fix a known issue).
|
||||
|
||||
#### Reviews may not be used as an effective veto for a PR because:
|
||||
- There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix.
|
||||
- It does not fit well with some other contributors' longer-term vision for the project.
|
||||
|
||||
## Releases
|
||||
|
||||
Declaring formal releases remains the prerogative of the project maintainer(s).
|
||||
|
||||
## Changes to this arrangement
|
||||
|
||||
This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
|
||||
|
||||
## Heritage
|
||||
|
||||
These contributing guidelines are modified from the "OPEN Open Source Project" guidelines for the Level project: [https://github.com/Level/community/blob/master/CONTRIBUTING.md](https://github.com/Level/community/blob/master/CONTRIBUTING.md)
|
||||
@@ -0,0 +1,76 @@
|
||||
1674 Jaco Bump deps (#1912)
|
||||
44 Tarik Gul Set pbkdf2Encode rounds to default to 210,000 (#1983)
|
||||
13 Carlo Sala feat(networks): add zeitgeist Ledger support (#1840)
|
||||
11 Valentin Fernandez 13.5.7 (#2008)
|
||||
9 Antoine Estienne Add eth uri keyring test (#1291)
|
||||
9 rajk93 14.0.1 (#2014)
|
||||
6 Amaury Martiny Add tests from Rust (#283)
|
||||
6 Jakub Pánik HydraDX -> Hydration rebrand (#1923)
|
||||
5 Arjun Porwal 13.5.6 (#2004)
|
||||
5 kwingram25 Add BN consts and timeToString (#610)
|
||||
4 Adam Dossa Update Polymesh Genesis Hash (#1193)
|
||||
4 pan add bifrost kusama (#1626)
|
||||
3 Luke Schoen docs: Fix example in readme since we use default imports (#243)
|
||||
2 AndreasGassmann feat(dependencies): remove moment.js (#530)
|
||||
2 Cameron Fairchild Add bittensor details (#1783)
|
||||
2 Gav Wood Fix stake:unstake (#182)
|
||||
2 Hyungsuk Kang Add cross client Encryption/Decryption (#496)
|
||||
2 Jared Norris Add Sora network (#1306)
|
||||
2 Kami Adding Westend genesis. (#1429)
|
||||
2 Lovesh Harchandani Update genesis hash of Dock mainnet (#1436)
|
||||
2 Nantian fix bigint type (#1079)
|
||||
2 Stefanie Doll Add missing dependencies to util-crypto (#424)
|
||||
1 Agustinus Theodorus docs: fix typo in naclSeal docs. (#1005)
|
||||
1 Aleš Ferlan feat: allow setting of thousand/decimal separators (#1722)
|
||||
1 Alex Sedighi fix Nodle's known network name (#1699)
|
||||
1 Alexander Krupenkin Added ECDSA keypair support (#589)
|
||||
1 alexdniep added equilibrium slip44; added equilibrium genesis hash (#984)
|
||||
1 amphineko chore: fix typo in versionDetect messages (#941)
|
||||
1 Axel Chalon Simplify @polkadot/keyring KeyringPair (#419)
|
||||
1 Brad Larson [FIX] Adjust error message (#646)
|
||||
1 Carlos Medeiros add hydradx to knownLedger (#1647)
|
||||
1 Chris Get rid of warnings for dual esm and cjs packages of the same version (#1890)
|
||||
1 Dmitrii Novikov Add `Vara` support to Generic Ledger App (#1958)
|
||||
1 Eliott Teissonniere add nodle ledger specs (#1089)
|
||||
1 Guilane DENGUIR feat(VTB): Added support for VTB blockchain (#1651)
|
||||
1 h4x3rotab Throws an error when signing with a locked keypair (#578)
|
||||
1 Harry Liu Update README.md (#446)
|
||||
1 henrikdent Add DENTNet ledger support (via generic Polkadot app) (#1942)
|
||||
1 Hoon Kim replace plasm genesis with astar (#1397)
|
||||
1 Hussein Ait-Lahcen Add Picasso Genesis Hash (#1261)
|
||||
1 Igor Support genshiro network (#1148)
|
||||
1 Jake Naviasky Add evmToAddress and addressToEvm crypto utils. (#705)
|
||||
1 JesseAbram added check if valid address to docs (#440)
|
||||
1 joe petrowski Add example for multisig address creation (#590)
|
||||
1 José Molina Colmenero Add ledger support for Mythos (#1969)
|
||||
1 kaelnew add ledger support for stafi (#1569)
|
||||
1 Leonardo Custodio Ledger support (#1879)
|
||||
1 Leonardo Razovic feat: add Polimec (#1945)
|
||||
1 Lezek123 logger - wildcard include/exclude with DEBUG env (#932)
|
||||
1 Lovish Arora Add peaq support for ledger (#1928)
|
||||
1 Mario Neises remove redudant export (#257)
|
||||
1 Miguel Hervas Add CFG Ledger Support (#1057)
|
||||
1 Mikhail Fedosov feat: add 3dpass network (#1769)
|
||||
1 Mohsan Riaz Add Ternoa for ledger hardware support (#1711)
|
||||
1 nahuseyoum Reserving 65 for Aventus based on the new extended range (#871)
|
||||
1 Nantian add acala ledger config (#1485)
|
||||
1 Paul Miller sr25519: switch from wasm to micro-sr25519 (#1971)
|
||||
1 peetzweg/ adds `isRiscV` guard function similar to isWasm (#1878)
|
||||
1 qwer951123 Add acala genesis (#1161)
|
||||
1 Radek Bochenek feat: add Liberland support to Generic Ledger App (#1989)
|
||||
1 Raoul Millais Fix yarn add instructions in x-global README (#873)
|
||||
1 Raymond Zhong Add Edgeware ledger app (#1006)
|
||||
1 Reto Trinkler Adding 4 as address type of ss58 for Katal Chain (#639)
|
||||
1 Rocco Musolino remove doc broken link (#697)
|
||||
1 Salina Add Creditcoin3 support to Generic Ledger App (#1985)
|
||||
1 Shamilkhan Add genesis hash for Cere Network (#1806)
|
||||
1 Song Zhou Add in ability to encrypt/decrypt msg with keyring (#1070)
|
||||
1 Thibaut Sardan add hasLedgerSupport prop to networks (#835)
|
||||
1 Tomokazu Kozuma fix comment (#1119)
|
||||
1 Tore19 Add stafi genesis hash (#1096)
|
||||
1 Wei Tang ss58: add Kulupu prefix to the list (#595)
|
||||
1 Wil Wade Add Frequency to the Generic Ledger App (#1955)
|
||||
1 WoeOm Handing high precision scenes (#450)
|
||||
1 xx network Add xxnetwork genesis hash and ledger app (#1463)
|
||||
1 Yaroslav Bolyukin Add quartz support for ledger, update unique slip-0044 identifier (#1882)
|
||||
1 Yuri Extra check for process for browser bundles (#923)
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,31 @@
|
||||
# @pezkuwi/common
|
||||
|
||||
Various useful utility functions that are used across all projects in the [@pezkuwi](https://pezkuwi.js.org) namespace. It provides utility functions with additional safety checks, allowing not only for consistent coding, but also reducing the general boilerplate.
|
||||
|
||||
## overview
|
||||
|
||||
This repository is split up into a number of internal packages, namely utilities -
|
||||
|
||||
- [keyring](packages/keyring/) Keyring management
|
||||
- [util](packages/util/) General utilities
|
||||
- [util-crypto](packages/util-crypto/) Crypto and hashing utilities
|
||||
|
||||
## development
|
||||
|
||||
Contributions are welcome!
|
||||
|
||||
To start off, this repo (along with others in the [@pezkuwi](https://github.com/polkadot-js/) family) uses yarn workspaces to organise the code. As such, after cloning, its dependencies _should_ be installed via `yarn`, not via npm; the latter will result in broken dependencies.
|
||||
|
||||
To get started -
|
||||
|
||||
1. Clone the repo locally, via `git clone https://github.com/pezkuwichain/pezkuwi-common <optional local path>`
|
||||
2. Ensure that you have a recent version of Node.js, for development purposes [Node 10](https://nodejs.org/en/) is recommended.
|
||||
3. Ensure that you have a recent version of Yarn, for development purposes [Yarn >=1.10.1](https://yarnpkg.com/docs/install) is required.
|
||||
4. Install the dependencies by running `yarn`
|
||||
5. Build the everything via `yarn run build`
|
||||
6. You can also launch the API Docs, via `yarn vuepress dev docs`
|
||||
7. Access the docs via [http://localhost:8080](http://localhost:8080)
|
||||
|
||||
## tutorials
|
||||
|
||||
Looking for tutorials to get started? Look at [examples](https://pezkuwi.js.org/api/examples/keyring/) for guides on how to use the base utilities.
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="initial-scale=0.5, maximum-scale=1">
|
||||
<meta http-equiv="refresh" content="0;URL='https://polkadot.js.org/docs/'" />
|
||||
<title>Redirecting to https://polkadot.js.org/docs/</title>
|
||||
</head>
|
||||
<body>
|
||||
Redirecting to <a href="https://polkadot.js.org/docs/">https://polkadot.js.org/docs/</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="initial-scale=0.5, maximum-scale=1">
|
||||
<meta http-equiv="refresh" content="0;URL='https://polkadot.js.org/docs/'" />
|
||||
<title>Redirecting to https://polkadot.js.org/docs/</title>
|
||||
</head>
|
||||
<body>
|
||||
Redirecting to <a href="https://polkadot.js.org/docs/">https://polkadot.js.org/docs/</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/util authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import baseConfig from '@pezkuwi/dev/config/eslint';
|
||||
|
||||
export default [
|
||||
...baseConfig
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"imports": {
|
||||
"https://esm.sh/v90/@types/bn.js@~5.2/index.d.ts": "https://esm.sh/v90/@types/bn.js@5.1.0/index.d.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"author": "Pezkuwi Team <team@pezkuwichain.io>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
|
||||
"engines": {
|
||||
"node": ">=18.14"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-common#readme",
|
||||
"license": "Apache-2.0",
|
||||
"packageManager": "yarn@4.9.1",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "14.0.1",
|
||||
"versions": {
|
||||
"git": "14.0.1",
|
||||
"npm": "14.0.1"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "polkadot-dev-build-ts",
|
||||
"build:networks": "polkadot-dev-run-test packages/networks/src",
|
||||
"build:release": "polkadot-ci-ghact-build",
|
||||
"build:rollup": "polkadot-exec-rollup --config",
|
||||
"clean": "polkadot-dev-clean-build",
|
||||
"deno": "yarn polkadot-dev-deno-map && yarn build && deno check --import-map=import_map.json mod.ts",
|
||||
"lint": "polkadot-dev-run-lint",
|
||||
"postinstall": "polkadot-dev-yarn-only",
|
||||
"test": "polkadot-dev-run-test --env browser ^mnemonic/toMiniSecretCmp",
|
||||
"test:mnemonicCmp": "polkadot-dev-run-test --env browser mnemonic/toMiniSecretCmp",
|
||||
"test:node": "polkadot-dev-run-test --env browser ^mnemonic/toMiniSecretCmp",
|
||||
"test:one": "polkadot-dev-run-test --env browser"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/dev": "^0.83.3",
|
||||
"@types/node": "^22.7.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# @pezkuwi/hw-ledger-transports
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/hw-ledger-transports#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/hw-ledger-transports",
|
||||
"repository": {
|
||||
"directory": "packages/hw-ledger-transports",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "14.0.1",
|
||||
"browser": "browser.js",
|
||||
"main": "node.js",
|
||||
"react-native": "react-native.js",
|
||||
"dependencies": {
|
||||
"@ledgerhq/hw-transport": "^6.31.4",
|
||||
"@ledgerhq/hw-transport-webhid": "^6.29.4",
|
||||
"@ledgerhq/hw-transport-webusb": "^6.29.4",
|
||||
"@pezkuwi/util": "14.0.1",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@ledgerhq/hw-transport-node-hid-singleton": "^6.31.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import LedgerHid from '@ledgerhq/hw-transport-webhid';
|
||||
import LedgerUsb from '@ledgerhq/hw-transport-webusb';
|
||||
|
||||
import { createDefs } from './util.js';
|
||||
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
|
||||
export const transports = /*#__PURE__*/ createDefs(['webusb', LedgerUsb], ['hid', LedgerHid]);
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { createDefs } from './util.js';
|
||||
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
|
||||
export const transports = /*#__PURE__*/ createDefs();
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './empty.js';
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import LedgerHid from '@ledgerhq/hw-transport-node-hid-singleton';
|
||||
|
||||
import { createDefs } from './util.js';
|
||||
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
|
||||
export const transports = /*#__PURE__*/ createDefs(['hid', LedgerHid]);
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger-transports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
// (packageInfo imports will be kept as-is, user-editable)
|
||||
|
||||
import { detectPackage } from '@pezkuwi/util';
|
||||
|
||||
import { packageInfo } from './packageInfo.js';
|
||||
|
||||
detectPackage(packageInfo, null, []);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger-transports authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/hw-ledger-transports', path: 'auto', type: 'auto', version: '14.0.1' };
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './empty.js';
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// CJS, so we use import * syntax
|
||||
import type * as HwTransport from '@ledgerhq/hw-transport';
|
||||
|
||||
// u2f is deprecated an therefore not added
|
||||
export type TransportType = 'hid' | 'webusb';
|
||||
|
||||
export type Transport = HwTransport.default;
|
||||
|
||||
export interface TransportDef {
|
||||
/** Create a transport to be used in Ledger operations */
|
||||
create (): Promise<Transport>;
|
||||
/** The type of the underlying transport definition */
|
||||
type: TransportType;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Transport, TransportDef, TransportType } from './types.js';
|
||||
|
||||
export function createDefs (...items: readonly [type: TransportType, Clazz: unknown][]): TransportDef[] {
|
||||
return items.map(([type, Clazz]): TransportDef => ({
|
||||
create: (): Promise<Transport> =>
|
||||
(Clazz as Pick<TransportDef, 'create'>).create(),
|
||||
type
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": [
|
||||
"**/mod.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../util/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
# @pezkuwi/hw-ledger
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/hw-ledger#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/hw-ledger",
|
||||
"repository": {
|
||||
"directory": "packages/hw-ledger",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
|
||||
},
|
||||
"sideEffects": [
|
||||
"./packageDetect.js",
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "14.0.1",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@pezkuwi/hw-ledger-transports": "14.0.1",
|
||||
"@pezkuwi/util": "14.0.1",
|
||||
"@zondax/ledger-substrate": "1.1.1",
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubstrateApp } from '@zondax/ledger-substrate';
|
||||
import type { TransportDef, TransportType } from '@pezkuwi/hw-ledger-transports/types';
|
||||
import type { AccountOptions, LedgerAddress, LedgerSignature, LedgerVersion } from './types.js';
|
||||
|
||||
import { newSubstrateApp } from '@zondax/ledger-substrate';
|
||||
|
||||
import { transports } from '@pezkuwi/hw-ledger-transports';
|
||||
import { hexAddPrefix, u8aToBuffer, u8aWrapBytes } from '@pezkuwi/util';
|
||||
|
||||
import { LEDGER_DEFAULT_ACCOUNT, LEDGER_DEFAULT_CHANGE, LEDGER_DEFAULT_INDEX, LEDGER_SUCCESS_CODE } from './constants.js';
|
||||
import { ledgerApps } from './defaults.js';
|
||||
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
|
||||
type Chain = keyof typeof ledgerApps;
|
||||
|
||||
type WrappedResult = Awaited<ReturnType<SubstrateApp['getAddress' | 'getVersion' | 'sign']>>;
|
||||
|
||||
/** @internal Wraps a SubstrateApp call, checking the result for any errors which result in a rejection */
|
||||
async function wrapError <T extends WrappedResult> (promise: Promise<T>): Promise<T> {
|
||||
const result = await promise;
|
||||
|
||||
if (result.return_code !== LEDGER_SUCCESS_CODE) {
|
||||
throw new Error(result.error_message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @internal Wraps a sign/signRaw call and returns the associated signature */
|
||||
function sign (method: 'sign' | 'signRaw', message: Uint8Array, accountOffset = 0, addressOffset = 0, { account = LEDGER_DEFAULT_ACCOUNT, addressIndex = LEDGER_DEFAULT_INDEX, change = LEDGER_DEFAULT_CHANGE }: Partial<AccountOptions> = {}): (app: SubstrateApp) => Promise<LedgerSignature> {
|
||||
return async (app: SubstrateApp): Promise<LedgerSignature> => {
|
||||
const { signature } = await wrapError(app[method](account + accountOffset, change, addressIndex + addressOffset, u8aToBuffer(message)));
|
||||
|
||||
return {
|
||||
signature: hexAddPrefix(signature.toString('hex'))
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Ledger
|
||||
*
|
||||
* @description
|
||||
* Legacy wrapper for a ledger app -
|
||||
* - it connects automatically on use, creating an underlying interface as required
|
||||
* - Promises reject with errors (unwrapped errors from @zondax/ledger-substrate)
|
||||
* @deprecated Use LedgerGeneric for up to date integration with ledger
|
||||
*/
|
||||
export class Ledger {
|
||||
readonly #ledgerName: string;
|
||||
readonly #transportDef: TransportDef;
|
||||
|
||||
#app: SubstrateApp | null = null;
|
||||
|
||||
constructor (transport: TransportType, chain: Chain) {
|
||||
const ledgerName = ledgerApps[chain];
|
||||
const transportDef = transports.find(({ type }) => type === transport);
|
||||
|
||||
if (!ledgerName) {
|
||||
throw new Error(`Unsupported Ledger chain ${chain}`);
|
||||
} else if (!transportDef) {
|
||||
throw new Error(`Unsupported Ledger transport ${transport}`);
|
||||
}
|
||||
|
||||
this.#ledgerName = ledgerName;
|
||||
this.#transportDef = transportDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address associated with a specific account & address offset. Optionally
|
||||
* asks for on-device confirmation
|
||||
*/
|
||||
public async getAddress (confirm = false, accountOffset = 0, addressOffset = 0, { account = LEDGER_DEFAULT_ACCOUNT, addressIndex = LEDGER_DEFAULT_INDEX, change = LEDGER_DEFAULT_CHANGE }: Partial<AccountOptions> = {}): Promise<LedgerAddress> {
|
||||
return this.withApp(async (app: SubstrateApp): Promise<LedgerAddress> => {
|
||||
const { address, pubKey } = await wrapError(app.getAddress(account + accountOffset, change, addressIndex + addressOffset, confirm));
|
||||
|
||||
return {
|
||||
address,
|
||||
publicKey: hexAddPrefix(pubKey)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the Ledger application on the device
|
||||
*/
|
||||
public async getVersion (): Promise<LedgerVersion> {
|
||||
return this.withApp(async (app: SubstrateApp): Promise<LedgerVersion> => {
|
||||
const { device_locked: isLocked, major, minor, patch, test_mode: isTestMode } = await wrapError(app.getVersion());
|
||||
|
||||
return {
|
||||
isLocked,
|
||||
isTestMode,
|
||||
version: [major, minor, patch]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a transaction on the Ledger device
|
||||
*/
|
||||
public async sign (message: Uint8Array, accountOffset?: number, addressOffset?: number, options?: Partial<AccountOptions>): Promise<LedgerSignature> {
|
||||
return this.withApp(sign('sign', message, accountOffset, addressOffset, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Signs a message (non-transactional) on the Ledger device
|
||||
*/
|
||||
public async signRaw (message: Uint8Array, accountOffset?: number, addressOffset?: number, options?: Partial<AccountOptions>): Promise<LedgerSignature> {
|
||||
return this.withApp(sign('signRaw', u8aWrapBytes(message), accountOffset, addressOffset, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Returns a created SubstrateApp to perform operations against. Generally
|
||||
* this is only used internally, to ensure consistent bahavior.
|
||||
*/
|
||||
async withApp <T> (fn: (app: SubstrateApp) => Promise<T>): Promise<T> {
|
||||
try {
|
||||
if (!this.#app) {
|
||||
const transport = await this.#transportDef.create();
|
||||
|
||||
// We need this override for the actual type passing - the Deno environment
|
||||
// is quite a bit stricter and it yields invalids between the two (specifically
|
||||
// since we mangle the imports from .default in the types for CJS/ESM and between
|
||||
// esm.sh versions this yields problematic outputs)
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
||||
this.#app = newSubstrateApp(transport as any, this.#ledgerName);
|
||||
}
|
||||
|
||||
return await fn(this.#app);
|
||||
} catch (error) {
|
||||
this.#app = null;
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TransportDef, TransportType } from '@pezkuwi/hw-ledger-transports/types';
|
||||
import type { AccountOptionsGeneric, LedgerAddress, LedgerSignature, LedgerVersion } from './types.js';
|
||||
|
||||
import { PolkadotGenericApp } from '@zondax/ledger-substrate';
|
||||
|
||||
import { transports } from '@pezkuwi/hw-ledger-transports';
|
||||
import { hexAddPrefix, u8aToBuffer, u8aWrapBytes } from '@pezkuwi/util';
|
||||
|
||||
import { ledgerApps } from './defaults.js';
|
||||
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
|
||||
type Chain = keyof typeof ledgerApps;
|
||||
|
||||
type WrappedResult = Awaited<ReturnType<PolkadotGenericApp['getAddress' | 'getVersion' | 'sign' | 'signWithMetadata']>>;
|
||||
|
||||
// FIXME This type is a copy of the `class ResponseError`
|
||||
// imported from `@zondax/ledger-js`. Happens because ledger-js includes
|
||||
// circular dependencies. This is a hack to avoid versioning issues
|
||||
// with Deno.
|
||||
interface ResponseError {
|
||||
errorMessage: string
|
||||
returnCode: number
|
||||
}
|
||||
|
||||
/** @internal Wraps a PolkadotGenericApp call, checking the result for any errors which result in a rejection */
|
||||
async function wrapError <T extends WrappedResult> (promise: Promise<T>): Promise<T> {
|
||||
let result: T;
|
||||
|
||||
try {
|
||||
result = await promise;
|
||||
} catch (e: unknown) {
|
||||
// We check to see if the propogated error is the newer ResponseError type.
|
||||
// The response code use to be part of the result, but with the latest breaking changes from 0.42.x
|
||||
// the interface and it's types have completely changed.
|
||||
if ((e as ResponseError).returnCode) {
|
||||
throw new Error(`${(e as ResponseError).returnCode}: ${(e as ResponseError).errorMessage}`);
|
||||
}
|
||||
|
||||
throw new Error((e as Error).message);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @internal Wraps a signEd25519/signRawEd25519 call and returns the associated signature */
|
||||
function sign (method: 'signEd25519' | 'signRawEd25519', message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
|
||||
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
|
||||
|
||||
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
|
||||
const { signature } = await wrapError(app[method](bip42Path, u8aToBuffer(message)));
|
||||
|
||||
return {
|
||||
signature: hexAddPrefix(signature.toString('hex'))
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal Wraps a signEcdsa/signRawEcdsa call and returns the associated signature */
|
||||
function signEcdsa (method: 'signEcdsa' | 'signRawEcdsa', message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
|
||||
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
|
||||
|
||||
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
|
||||
const { r, s, v } = await wrapError(app[method](bip42Path, u8aToBuffer(message)));
|
||||
|
||||
const signature = Buffer.concat([r, s, v]);
|
||||
|
||||
return {
|
||||
signature: hexAddPrefix(signature.toString('hex'))
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal Wraps a signWithMetadataEd25519 call and returns the associated signature */
|
||||
function signWithMetadata (message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0, { metadata }: Partial<AccountOptionsGeneric> = {}): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
|
||||
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
|
||||
|
||||
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
|
||||
if (!metadata) {
|
||||
throw new Error('The metadata option must be present when using signWithMetadata');
|
||||
}
|
||||
|
||||
const bufferMsg = Buffer.from(message);
|
||||
|
||||
const { signature } = await wrapError(app.signWithMetadataEd25519(bip42Path, bufferMsg, metadata));
|
||||
|
||||
return {
|
||||
signature: hexAddPrefix(signature.toString('hex'))
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal Wraps a signWithMetadataEcdsa call and returns the associated signature */
|
||||
function signWithMetadataEcdsa (message: Uint8Array, slip44: number, accountIndex = 0, addressOffset = 0, { metadata }: Partial<AccountOptionsGeneric> = {}): (app: PolkadotGenericApp) => Promise<LedgerSignature> {
|
||||
const bip42Path = `m/44'/${slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
|
||||
|
||||
return async (app: PolkadotGenericApp): Promise<LedgerSignature> => {
|
||||
if (!metadata) {
|
||||
throw new Error('The metadata option must be present when using signWithMetadata');
|
||||
}
|
||||
|
||||
const bufferMsg = Buffer.from(message);
|
||||
|
||||
const { r, s, v } = await wrapError(app.signWithMetadataEcdsa(bip42Path, bufferMsg, metadata));
|
||||
|
||||
const signature = Buffer.concat([r, s, v]);
|
||||
|
||||
return {
|
||||
signature: hexAddPrefix(signature.toString('hex'))
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @name Ledger
|
||||
*
|
||||
* @description
|
||||
* A very basic wrapper for a ledger app -
|
||||
* - it connects automatically on use, creating an underlying interface as required
|
||||
* - Promises reject with errors (unwrapped errors from @zondax/ledger-substrate-js)
|
||||
*/
|
||||
export class LedgerGeneric {
|
||||
readonly #transportDef: TransportDef;
|
||||
readonly #slip44: number;
|
||||
/**
|
||||
* The chainId is represented by the chains token in all lowercase. Example: Polkadot -> dot
|
||||
*/
|
||||
readonly #chainId?: string;
|
||||
/**
|
||||
* The metaUrl is seen as a server url that the underlying `PolkadotGenericApp` will use to
|
||||
* retrieve the signature given a tx blob, and a chainId. It is important to note that if you would like to avoid
|
||||
* having any network calls made, use `signWithMetadata`, and avoid `sign`.
|
||||
*/
|
||||
readonly #metaUrl?: string;
|
||||
|
||||
#app: PolkadotGenericApp | null = null;
|
||||
|
||||
constructor (transport: TransportType, chain: Chain, slip44: number, chainId?: string, metaUrl?: string) {
|
||||
const ledgerName = ledgerApps[chain];
|
||||
const transportDef = transports.find(({ type }) => type === transport);
|
||||
|
||||
if (!ledgerName) {
|
||||
throw new Error(`Unsupported Ledger chain ${chain}`);
|
||||
} else if (!transportDef) {
|
||||
throw new Error(`Unsupported Ledger transport ${transport}`);
|
||||
}
|
||||
|
||||
this.#metaUrl = metaUrl;
|
||||
this.#chainId = chainId;
|
||||
this.#slip44 = slip44;
|
||||
this.#transportDef = transportDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the address associated with a specific Ed25519 account & address offset. Optionally
|
||||
* asks for on-device confirmation
|
||||
*/
|
||||
public async getAddress (ss58Prefix: number, confirm = false, accountIndex = 0, addressOffset = 0): Promise<LedgerAddress> {
|
||||
const bip42Path = `m/44'/${this.#slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
|
||||
|
||||
return this.withApp(async (app: PolkadotGenericApp): Promise<LedgerAddress> => {
|
||||
const { address, pubKey } = await wrapError(app.getAddressEd25519(bip42Path, ss58Prefix, confirm));
|
||||
|
||||
return {
|
||||
address,
|
||||
publicKey: hexAddPrefix(pubKey)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the address associated with a specific ecdsa account & address offset. Optionally
|
||||
* asks for on-device confirmation
|
||||
*/
|
||||
public async getAddressEcdsa (confirm = false, accountIndex = 0, addressOffset = 0) {
|
||||
const bip42Path = `m/44'/${this.#slip44}'/${accountIndex}'/${0}'/${addressOffset}'`;
|
||||
|
||||
return this.withApp(async (app: PolkadotGenericApp): Promise<LedgerAddress> => {
|
||||
const { address, pubKey } = await wrapError(app.getAddressEcdsa(bip42Path, confirm));
|
||||
|
||||
return {
|
||||
address,
|
||||
publicKey: hexAddPrefix(pubKey)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the version of the Ledger application on the device
|
||||
*/
|
||||
public async getVersion (): Promise<LedgerVersion> {
|
||||
return this.withApp(async (app: PolkadotGenericApp): Promise<LedgerVersion> => {
|
||||
const { deviceLocked: isLocked, major, minor, patch, testMode: isTestMode } = await wrapError(app.getVersion());
|
||||
|
||||
return {
|
||||
isLocked: !!isLocked,
|
||||
isTestMode: !!isTestMode,
|
||||
version: [major || 0, minor || 0, patch || 0]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Signs a transaction on the Ledger device. This requires the LedgerGeneric class to be instantiated with `chainId`, and `metaUrl`
|
||||
*/
|
||||
public async sign (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
|
||||
return this.withApp(sign('signEd25519', message, this.#slip44, accountIndex, addressOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Signs a message (non-transactional) on the Ledger device
|
||||
*/
|
||||
public async signRaw (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
|
||||
return this.withApp(sign('signRawEd25519', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Signs a transaction on the Ledger device with Ecdsa. This requires the LedgerGeneric class to be instantiated with `chainId`, and `metaUrl`
|
||||
*/
|
||||
public async signEcdsa (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
|
||||
return this.withApp(signEcdsa('signEcdsa', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Signs a message with Ecdsa (non-transactional) on the Ledger device
|
||||
*/
|
||||
public async signRawEcdsa (message: Uint8Array, accountIndex?: number, addressOffset?: number): Promise<LedgerSignature> {
|
||||
return this.withApp(signEcdsa('signRawEcdsa', u8aWrapBytes(message), this.#slip44, accountIndex, addressOffset));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Signs a transaction on the ledger device provided some metadata.
|
||||
*/
|
||||
public async signWithMetadata (message: Uint8Array, accountIndex?: number, addressOffset?: number, options?: Partial<AccountOptionsGeneric>): Promise<LedgerSignature> {
|
||||
return this.withApp(signWithMetadata(message, this.#slip44, accountIndex, addressOffset, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Signs a transaction on the ledger device for an ecdsa signature provided some metadata.
|
||||
*/
|
||||
public async signWithMetadataEcdsa (message: Uint8Array, accountIndex?: number, addressOffset?: number, options?: Partial<AccountOptionsGeneric>) {
|
||||
return this.withApp(signWithMetadataEcdsa(message, this.#slip44, accountIndex, addressOffset, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* Returns a created PolkadotGenericApp to perform operations against. Generally
|
||||
* this is only used internally, to ensure consistent bahavior.
|
||||
*/
|
||||
async withApp <T> (fn: (app: PolkadotGenericApp) => Promise<T>): Promise<T> {
|
||||
try {
|
||||
if (!this.#app) {
|
||||
const transport = await this.#transportDef.create();
|
||||
|
||||
// We need this override for the actual type passing - the Deno environment
|
||||
// is quite a bit stricter and it yields invalids between the two (specifically
|
||||
// since we mangle the imports from .default in the types for CJS/ESM and between
|
||||
// esm.sh versions this yields problematic outputs)
|
||||
//
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
||||
this.#app = new PolkadotGenericApp(transport as any, this.#chainId, this.#metaUrl);
|
||||
}
|
||||
|
||||
return await fn(this.#app);
|
||||
} catch (error) {
|
||||
this.#app = null;
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// This is necessary to ensure users still have access to class Ledger even though its deprecated.
|
||||
//
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
export { Ledger } from './Ledger.js';
|
||||
export { LedgerGeneric } from './LedgerGeneric.js';
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export const LEDGER_DEFAULT_ACCOUNT = 0x80000000;
|
||||
|
||||
export const LEDGER_DEFAULT_CHANGE = 0x80000000;
|
||||
|
||||
export const LEDGER_DEFAULT_INDEX = 0x80000000;
|
||||
|
||||
export const LEDGER_SUCCESS_CODE = 0x9000;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { supportedApps } from '@zondax/ledger-substrate';
|
||||
|
||||
import { prevLedgerRecord } from './defaults.js';
|
||||
|
||||
describe('ledgerApps', (): void => {
|
||||
for (const k of Object.keys(prevLedgerRecord)) {
|
||||
it(`${k} is available in @zondax/ledger-substrate`, (): void => {
|
||||
expect(
|
||||
supportedApps.find(({ name }) =>
|
||||
name === prevLedgerRecord[k]
|
||||
)
|
||||
).toBeDefined();
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// These map to the known name in the @zondax/ledger-substrate/supported_apps package
|
||||
// but they do not reflect all ledger apps that are supported. Since ledger now has support for all
|
||||
// substrate chains via the PolkadotGenericApp, any new chains that need ledger support can be added to
|
||||
// `genericLedgerApps` below.
|
||||
export const prevLedgerRecord: Record<string, string> = {
|
||||
acala: 'Acala',
|
||||
ajuna: 'Ajuna',
|
||||
'aleph-node': 'AlephZero',
|
||||
astar: 'Astar',
|
||||
bifrost: 'Bifrost',
|
||||
'bifrost-kusama': 'BifrostKusama',
|
||||
centrifuge: 'Centrifuge',
|
||||
composable: 'Composable',
|
||||
darwinia: 'Darwinia',
|
||||
'dock-mainnet': 'Dock',
|
||||
edgeware: 'Edgeware',
|
||||
enjin: 'Enjin',
|
||||
equilibrium: 'Equilibrium',
|
||||
genshiro: 'Genshiro',
|
||||
hydradx: 'HydraDX',
|
||||
'interlay-parachain': 'Interlay',
|
||||
karura: 'Karura',
|
||||
khala: 'Khala',
|
||||
kusama: 'Kusama',
|
||||
matrixchain: 'Matrixchain',
|
||||
nodle: 'Nodle',
|
||||
origintrail: 'OriginTrail',
|
||||
parallel: 'Parallel',
|
||||
peaq: 'Peaq',
|
||||
pendulum: 'Pendulum',
|
||||
phala: 'Phala',
|
||||
picasso: 'Picasso',
|
||||
polkadex: 'Polkadex',
|
||||
polkadot: 'Polkadot',
|
||||
polymesh: 'Polymesh',
|
||||
quartz: 'Quartz',
|
||||
sora: 'Sora',
|
||||
stafi: 'Stafi',
|
||||
statemine: 'Statemine',
|
||||
statemint: 'Statemint',
|
||||
ternoa: 'Ternoa',
|
||||
unique: 'Unique',
|
||||
vtb: 'VTB',
|
||||
xxnetwork: 'XXNetwork',
|
||||
zeitgeist: 'Zeitgeist'
|
||||
};
|
||||
|
||||
// Any chains moving forward that are supported by the PolkadotGenericApp from ledger will input their names below.
|
||||
export const genericLedgerApps = {
|
||||
bittensor: 'Bittensor',
|
||||
creditcoin3: 'Creditcoin3',
|
||||
dentnet: 'DENTNet',
|
||||
encointer: 'Encointer',
|
||||
frequency: 'Frequency',
|
||||
integritee: 'Integritee',
|
||||
liberland: 'Liberland',
|
||||
mythos: 'Mythos',
|
||||
polimec: 'Polimec',
|
||||
vara: 'Vara'
|
||||
};
|
||||
|
||||
// These match up with the keys of the knownLedger object in the @polkadot/networks/defaults/ledger.ts
|
||||
export const ledgerApps: Record<string, string> = {
|
||||
...prevLedgerRecord,
|
||||
...genericLedgerApps
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import './packageDetect.js';
|
||||
|
||||
export * from './bundle.js';
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './index.js';
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
// (packageInfo imports will be kept as-is, user-editable)
|
||||
|
||||
import { packageInfo as transportInfo } from '@pezkuwi/hw-ledger-transports/packageInfo';
|
||||
import { detectPackage } from '@pezkuwi/util';
|
||||
import { packageInfo as utilInfo } from '@pezkuwi/util/packageInfo';
|
||||
|
||||
import { packageInfo } from './packageInfo.js';
|
||||
|
||||
detectPackage(packageInfo, null, [transportInfo, utilInfo]);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/hw-ledger', path: 'auto', type: 'auto', version: '14.0.1' };
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017-2025 @polkadot/hw-ledger authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
/**
|
||||
* Legacy Type that works with the `Ledger` class.
|
||||
*/
|
||||
export interface AccountOptions {
|
||||
/** The index of the account */
|
||||
account: number;
|
||||
/** The index of the address */
|
||||
addressIndex: number;
|
||||
/** The change to apply */
|
||||
change: number;
|
||||
}
|
||||
|
||||
export interface AccountOptionsGeneric extends AccountOptions {
|
||||
/** Option for PolkadotGenericApp.signWithMetadata */
|
||||
metadata: Buffer;
|
||||
}
|
||||
|
||||
export interface LedgerAddress {
|
||||
/** The ss58 encoded address */
|
||||
address: string;
|
||||
/** The hex-encoded publicKey */
|
||||
publicKey: HexString;
|
||||
}
|
||||
|
||||
export interface LedgerSignature {
|
||||
/** A hex-encoded signature, as generated by the device */
|
||||
signature: HexString;
|
||||
}
|
||||
|
||||
export interface LedgerVersion {
|
||||
/** Indicator flag for locked status */
|
||||
isLocked: boolean;
|
||||
/** Indicator flag for testmode status */
|
||||
isTestMode: boolean;
|
||||
/** The software version for this device */
|
||||
version: [major: number, minor: number, patch: number];
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/mod.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../hw-ledger-transports/tsconfig.build.json" },
|
||||
{ "path": "../util/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../hw-ledger/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# @pezkuwi/keyring
|
||||
|
||||
Key management of user accounts including generation and retrieval of keyring pairs from a variety of input combinations.
|
||||
|
||||
## Usage
|
||||
|
||||
Installation -
|
||||
|
||||
```
|
||||
yarn add @pezkuwi/keyring
|
||||
```
|
||||
|
||||
Classes and Functions can be imported as follows:
|
||||
|
||||
```js
|
||||
import Keyring from '@pezkuwi/keyring';
|
||||
```
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
|
||||
"description": "Keyring management",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/keyring#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/keyring",
|
||||
"repository": {
|
||||
"directory": "packages/keyring",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
|
||||
},
|
||||
"sideEffects": [
|
||||
"./packageDetect.js",
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "14.0.1",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@pezkuwi/util": "14.0.1",
|
||||
"@pezkuwi/util-crypto": "14.0.1",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@pezkuwi/util": "14.0.1",
|
||||
"@pezkuwi/util-crypto": "14.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// all external
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
export { decodeAddress, encodeAddress, setSS58Format } from '@pezkuwi/util-crypto';
|
||||
|
||||
// all named
|
||||
export { Keyring } from './keyring.js';
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
export { createPair } from './pair/index.js';
|
||||
export { createTestKeyring } from './testing.js';
|
||||
export { createTestPairs } from './testingPairs.js';
|
||||
|
||||
// all starred
|
||||
export * from './defaults.js';
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// default substrate dev phrase
|
||||
export const DEV_PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
|
||||
|
||||
// seed from the above phrase
|
||||
export const DEV_SEED = '0xfac7959dbfe72f052e5a0c3c8d6530f202b02fd8f9f5ca3580ec8deb7797479e';
|
||||
@@ -0,0 +1,609 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import type { KeyringPair$Json } from './types.js';
|
||||
|
||||
import { hexToU8a, stringToU8a } from '@pezkuwi/util';
|
||||
import { base64Decode, cryptoWaitReady, encodeAddress, mnemonicGenerate, randomAsU8a, setSS58Format } from '@pezkuwi/util-crypto';
|
||||
import * as languages from '@pezkuwi/util-crypto/mnemonic/wordlists/index';
|
||||
|
||||
import { decodePair } from './pair/decode.js';
|
||||
import Keyring from './index.js';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('keypair', (): void => {
|
||||
describe('ed25519', (): void => {
|
||||
const publicKeyOne = new Uint8Array([47, 140, 97, 41, 216, 22, 207, 81, 195, 116, 188, 127, 8, 195, 230, 62, 209, 86, 207, 120, 174, 251, 74, 101, 80, 217, 123, 135, 153, 121, 119, 238]);
|
||||
const publicKeyTwo = new Uint8Array([215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26]);
|
||||
const seedOne = stringToU8a('12345678901234567890123456789012');
|
||||
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42, type: 'ed25519' });
|
||||
|
||||
keyring.addFromSeed(seedOne, {});
|
||||
});
|
||||
|
||||
it('adds the pair', (): void => {
|
||||
expect(
|
||||
keyring.addFromSeed(seedTwo, {}).publicKey
|
||||
).toEqual(publicKeyTwo);
|
||||
});
|
||||
|
||||
it('creates via a dev seed', (): void => {
|
||||
expect(
|
||||
keyring.addFromUri('//Alice').address
|
||||
).toEqual('5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu');
|
||||
});
|
||||
|
||||
it('creates a ed25519 pair via mnemonicToSeed', (): void => {
|
||||
expect(
|
||||
keyring.addFromUri(
|
||||
'seed sock milk update focus rotate barely fade car face mechanic mercy'
|
||||
).address
|
||||
).toEqual('5DkQP32jP4DVJLWWBRBoZF2tpWjqFrcrTBo6H5NcSk7MxKCC');
|
||||
});
|
||||
|
||||
it('adds from a mnemonic, with correct ss58', (): void => {
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
setSS58Format(20); // this would not be used
|
||||
keyring.setSS58Format(2); // this would be used
|
||||
|
||||
const pair = keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {});
|
||||
|
||||
expect(pair.address).toEqual('HSLu2eci2GCfWkRimjjdTXKoFSDL3rBv5Ey2JWCBj68cVZj');
|
||||
expect(encodeAddress(pair.publicKey)).toEqual('35cDYtPsdG1HUa2n2MaARgJyRz1WKMBZK1DL6c5cX7nugQh1');
|
||||
});
|
||||
|
||||
it('allows publicKeys retrieval', (): void => {
|
||||
keyring.addFromSeed(seedTwo, {});
|
||||
|
||||
expect(
|
||||
keyring.getPublicKeys()
|
||||
).toEqual([publicKeyOne, publicKeyTwo]);
|
||||
});
|
||||
|
||||
it('allows retrieval of a specific item', (): void => {
|
||||
expect(
|
||||
keyring.getPair(publicKeyOne).publicKey
|
||||
).toEqual(publicKeyOne);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
expect(
|
||||
keyring.addFromJson(
|
||||
JSON.parse('{"address":"5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua","encoded":"0xb4a14995d25ab609f3686e9fa45f1fb237cd833f33f00d4b12c51858ca070d96972e47d73aae5eeb0fc06f923826cf0943fdb02c2c2ee30ef52a7912663053940d1da4da66b3a3f520ae07422c1c94b2d95690fca9d1f4a997623bb2923a8833280e19e7f72c3c5cfa343974e60e2b3dc53b404fdaf330756daad5e4e3","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"0"},"meta":{"isTesting":true,"name":"alice"}}') as KeyringPair$Json
|
||||
).publicKey
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
});
|
||||
|
||||
it('signs and verifies', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE);
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('signs and verifies (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE, { withType: true });
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sr25519', (): void => {
|
||||
const publicKeyOne = new Uint8Array([116, 28, 8, 160, 111, 65, 197, 150, 96, 143, 103, 116, 37, 155, 217, 4, 51, 4, 173, 250, 93, 62, 234, 98, 118, 11, 217, 190, 151, 99, 77, 99]);
|
||||
const publicKeyTwo = hexToU8a('0x44a996beb1eef7bdcab976ab6d2ca26104834164ecf28fb375600576fcc6eb0f');
|
||||
const seedOne = stringToU8a('12345678901234567890123456789012');
|
||||
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42, type: 'sr25519' });
|
||||
|
||||
keyring.addFromSeed(seedOne, {});
|
||||
});
|
||||
|
||||
it('creates with dev phrase when only path specified', (): void => {
|
||||
expect(
|
||||
keyring.createFromUri('//Alice').address
|
||||
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
});
|
||||
|
||||
it('creates with integer derivations', (): void => {
|
||||
// MAX_SAFE_INTEGER
|
||||
expect(
|
||||
keyring.createFromUri('//9007199254740991').address
|
||||
).toEqual('5CDsyNZyqxLpHnTvknr68anUcYoBFjZbFKiEJJf4prB75Uog');
|
||||
|
||||
// MAX_SAFE_INTEGER + extra digits
|
||||
expect(
|
||||
keyring.createFromUri('//900719925474099999').address
|
||||
).toEqual('5GHj2D7RG2m2DXYwGSDpXwuuxn53G987i7p2EQVDqP4NYu4q');
|
||||
});
|
||||
|
||||
it('creates via dev seed (2-byte encoding)', (): void => {
|
||||
keyring.setSS58Format(252);
|
||||
|
||||
expect(
|
||||
keyring.addFromUri('//Alice').address
|
||||
).toEqual('xw8P6urbSAronL3zZFB7dg8p7LLSgKCUFDUgjohnf1iP434ic');
|
||||
});
|
||||
|
||||
it('adds the pair', (): void => {
|
||||
expect(
|
||||
keyring.addFromSeed(seedTwo, {}).publicKey
|
||||
).toEqual(publicKeyTwo);
|
||||
});
|
||||
|
||||
it('adds from a mnemonic', (): void => {
|
||||
keyring.setSS58Format(2);
|
||||
|
||||
expect(
|
||||
keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {}).address
|
||||
).toEqual('FSjXNRT2K1R5caeHLPD6WMrqYUpfGZB7ua8W89JFctZ1YqV');
|
||||
});
|
||||
|
||||
it('allows publicKeys retrieval', (): void => {
|
||||
keyring.addFromSeed(seedTwo, {});
|
||||
|
||||
expect(
|
||||
keyring.getPublicKeys()
|
||||
).toEqual([publicKeyOne, publicKeyTwo]);
|
||||
});
|
||||
|
||||
it('allows retrieval of a specific item', (): void => {
|
||||
expect(
|
||||
keyring.getPair(publicKeyOne).publicKey
|
||||
).toEqual(publicKeyOne);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
expect(
|
||||
keyring.addFromJson(
|
||||
JSON.parse('{"address":"5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua","encoded":"0xb4a14995d25ab609f3686e9fa45f1fb237cd833f33f00d4b12c51858ca070d96972e47d73aae5eeb0fc06f923826cf0943fdb02c2c2ee30ef52a7912663053940d1da4da66b3a3f520ae07422c1c94b2d95690fca9d1f4a997623bb2923a8833280e19e7f72c3c5cfa343974e60e2b3dc53b404fdaf330756daad5e4e3","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"0"},"meta":{"isTesting":true,"name":"alice"}}') as KeyringPair$Json
|
||||
).publicKey
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
});
|
||||
|
||||
it('signs and verifies', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE);
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('signs and verifies (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE, { withType: true });
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ecdsa', (): void => {
|
||||
const seedOne = 'potato act energy ahead stone taxi receive fame gossip equip chest round';
|
||||
const seedTwo = hexToU8a('0x3c74be003bd9a876be439949ccf2b292bd966c94959a689173b295b326cd6da7');
|
||||
const publicKeyOne = hexToU8a('0x02c6b6c664db5ef505477bba1cf2f1789c98796b9bb5fa21abd0ac4589bed980e7');
|
||||
const publicKeyTwo = hexToU8a('0x021da683b913fb28c979ba3e5f1881415cef4b1f58a5d05ed3610a2995e7b4943c');
|
||||
const addressKeyOne = hexToU8a('0x0cfd0dd2c59a9987b9848919163931b6a42283ffd3d91e92c98b522525a7038f');
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42, type: 'ecdsa' });
|
||||
|
||||
keyring.addFromMnemonic(seedOne, {});
|
||||
});
|
||||
|
||||
it('creates with dev phrase when only path specified', (): void => {
|
||||
expect(
|
||||
keyring.createFromUri('//Alice').address
|
||||
).toEqual('5C7C2Z5sWbytvHpuLTvzKunnnRwQxft1jiqrLD5rhucQ5S9X');
|
||||
});
|
||||
|
||||
it('adds the pair', (): void => {
|
||||
expect(
|
||||
keyring.addFromSeed(seedTwo, {}).publicKey
|
||||
).toEqual(publicKeyTwo);
|
||||
});
|
||||
|
||||
it('adds from a mnemonic', (): void => {
|
||||
keyring.setSS58Format(2);
|
||||
|
||||
expect(
|
||||
keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card').address
|
||||
).toEqual('DrRE1KAcs4pCicX8yJPh7YxkLPQ2vXnCFSVRPQfx38KjEFe');
|
||||
});
|
||||
|
||||
it('allows publicKeys retrieval', (): void => {
|
||||
keyring.addFromSeed(seedTwo, {});
|
||||
|
||||
expect(
|
||||
keyring.getPublicKeys()
|
||||
).toEqual([publicKeyOne, publicKeyTwo]);
|
||||
});
|
||||
|
||||
it('allows retrieval of a specific item', (): void => {
|
||||
expect(
|
||||
keyring.getPair(addressKeyOne).publicKey
|
||||
).toEqual(publicKeyOne);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
expect(
|
||||
keyring.addFromJson(
|
||||
JSON.parse('{"address":"5DzMsaYFhmpRdErWrP6K6PD7UXzYoeETToSBUrZSvxasqWRz","encoded":"0xa192d39b42bc1601bf61df31039a554228593fadf870bc837b658a5114627aca199fff596260c95fe8994c66a47636cf0270aa08f402ba5541038753960d00e6c3af5e239ec58fb1eef3db7d6bc266f4853bdfe4ed17122d9092d879014d53980d2ee57f6f55a88c38836447d8645008e8815379626addc8f81f80cd49a2","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"2"},"meta":{}}') as KeyringPair$Json
|
||||
).address
|
||||
).toEqual('5DzMsaYFhmpRdErWrP6K6PD7UXzYoeETToSBUrZSvxasqWRz');
|
||||
});
|
||||
|
||||
it('allows creation from JSON', (): void => {
|
||||
keyring.setSS58Format(2);
|
||||
const pair = keyring.createFromJson(
|
||||
JSON.parse('{"address":"0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad","encoded":"qIhAhKqtf2iyEoWEr8nmBdksSI8EHHCpgJHToqd6Pl8AgAAAAQAAAAgAAADDZ//fj/BRRj+0+bl1KAlYgoPJp6nEUwiw0fVqO2BW4mjEgQ+iWwJEgDf1JUtecbzOlfhTXBzqX/dIYzLgUADrF4EFEPpboCWiU1iN7W/3DM1cOTRVvTGcbdIqW//z3axhz961qzeJVUIFgllwGe/euLUPIlKbIkiN/CsRYdQ=","encoding":{"content":["pkcs8","ecdsa"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"ecdsa","tags":[],"whenCreated":1600925898271}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(pair.address).toEqual('DHL8HKFuTTR55JzzLmkJRCAfPBbuevKaT9cXikxbEV97Ko8');
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad'));
|
||||
});
|
||||
|
||||
it('fails toJson() when password is incorrect', (): void => {
|
||||
const pair = keyring.createFromJson(
|
||||
JSON.parse('{"address":"0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad","encoded":"qIhAhKqtf2iyEoWEr8nmBdksSI8EHHCpgJHToqd6Pl8AgAAAAQAAAAgAAADDZ//fj/BRRj+0+bl1KAlYgoPJp6nEUwiw0fVqO2BW4mjEgQ+iWwJEgDf1JUtecbzOlfhTXBzqX/dIYzLgUADrF4EFEPpboCWiU1iN7W/3DM1cOTRVvTGcbdIqW//z3axhz961qzeJVUIFgllwGe/euLUPIlKbIkiN/CsRYdQ=","encoding":{"content":["pkcs8","ecdsa"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"ecdsa","tags":[],"whenCreated":1600925898271}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(
|
||||
() => pair.toJson('invalid')
|
||||
).toThrow(/Unable to decode using the supplied passphrase/);
|
||||
});
|
||||
|
||||
it('pass toJson() when password is correct', (): void => {
|
||||
const pair = keyring.createFromJson(
|
||||
JSON.parse('{"address":"0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad","encoded":"qIhAhKqtf2iyEoWEr8nmBdksSI8EHHCpgJHToqd6Pl8AgAAAAQAAAAgAAADDZ//fj/BRRj+0+bl1KAlYgoPJp6nEUwiw0fVqO2BW4mjEgQ+iWwJEgDf1JUtecbzOlfhTXBzqX/dIYzLgUADrF4EFEPpboCWiU1iN7W/3DM1cOTRVvTGcbdIqW//z3axhz961qzeJVUIFgllwGe/euLUPIlKbIkiN/CsRYdQ=","encoding":{"content":["pkcs8","ecdsa"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"ecdsa","tags":[],"whenCreated":1600925898271}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(
|
||||
() => pair.toJson('testing')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('encodes a pair toJSON (and decodes)', (): void => {
|
||||
const pair = keyring.createFromUri('moral movie very draw assault whisper awful rebuild speed purity repeat card');
|
||||
const json = pair.toJson('password');
|
||||
|
||||
expect(json.address).toEqual('0x03ddca309bd5fedd01f914d6fb76f23aa848a2a520802159215dba5085d7863619');
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'ecdsa'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
|
||||
const newPair = keyring.createFromJson(json);
|
||||
|
||||
expect(newPair.publicKey).toEqual(pair.publicKey);
|
||||
expect(
|
||||
() => newPair.unlock('password')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('signs and verifies', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(addressKeyOne);
|
||||
const signature = pair.sign(MESSAGE);
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('signs and verifies (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(addressKeyOne);
|
||||
const signature = pair.sign(MESSAGE, { withType: true });
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ethereum', (): void => {
|
||||
// combine mnemonic with derivation path
|
||||
const PHRASE = 'seed sock milk update focus rotate barely fade car face mechanic mercy' + '/m/44\'/60\'/0\'/0/0';
|
||||
const PRIV_KEY_ONE = '0x070dc3117300011918e26b02176945cc15c3d548cf49fd8418d97f93af699e46';
|
||||
const ETH_ADDRESS_ONE = '0x31ea8795EE32D782C8ff41a5C68Dcbf0F5B27f6d';
|
||||
const ETH_ADDRESS_TWO = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
|
||||
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ type: 'ethereum' });
|
||||
});
|
||||
|
||||
it('creates with dev phrase from the private key', (): void => {
|
||||
const pair = keyring.addFromSeed(hexToU8a(PRIV_KEY_ONE));
|
||||
|
||||
expect(
|
||||
pair.address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase from the private key in createFromUri', (): void => {
|
||||
const pair = keyring.createFromUri(PRIV_KEY_ONE);
|
||||
|
||||
expect(
|
||||
pair.address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase with derivation path specified', (): void => {
|
||||
const pair = keyring.createFromUri(PHRASE);
|
||||
|
||||
expect(
|
||||
pair.address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase with derivation path specified - addFromUri', (): void => {
|
||||
expect(
|
||||
keyring.addFromUri(PHRASE).address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase with derivation path specified - addFromUri with type', (): void => {
|
||||
const keyringUntyped = new Keyring();
|
||||
|
||||
expect(
|
||||
keyringUntyped.addFromUri(PHRASE, {}, 'ethereum').address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('encodes a pair toJSON (and decodes)', (): void => {
|
||||
const pair = keyring.createFromUri(PHRASE);
|
||||
const json = pair.toJson('password');
|
||||
|
||||
expect(json.address).toEqual('0x0381351b1b46d2602b0992bb5d5531f9c1696b0812feb2534b6884adc47e2e1d8b'); // this is the public key (different from address for ethereum)
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'ethereum'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
|
||||
const newPair = keyring.createFromJson(json);
|
||||
|
||||
expect(newPair.publicKey).toEqual(pair.publicKey);
|
||||
expect(
|
||||
() => newPair.unlock('password')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('encodes a pair toJSON and back', (): void => {
|
||||
const pairOriginal = keyring.createFromUri(PHRASE);
|
||||
const json = pairOriginal.toJson('password');
|
||||
const pair = keyring.addFromJson(
|
||||
json
|
||||
);
|
||||
|
||||
expect(pair.address).toEqual(ETH_ADDRESS_ONE);
|
||||
|
||||
pair.decodePkcs8('password');
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
const pair = keyring.addFromJson(
|
||||
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
|
||||
expect(pair.address).toEqual(ETH_ADDRESS_TWO);
|
||||
|
||||
pair.decodePkcs8('password');
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
|
||||
expect(pair.address).toBe(ETH_ADDRESS_TWO);
|
||||
});
|
||||
|
||||
it('allows for signing/verification', (): void => {
|
||||
const MESSAGE = stringToU8a('just some test message');
|
||||
const signer = keyring.createFromUri(PHRASE);
|
||||
const verifier = keyring.addFromJson(
|
||||
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
const signature = signer.sign(MESSAGE);
|
||||
const dummyPublic = verifier.publicKey.slice();
|
||||
|
||||
dummyPublic[dummyPublic.length - 1] = 0;
|
||||
|
||||
expect(verifier.verify(MESSAGE, signature, signer.publicKey)).toBe(true);
|
||||
expect(verifier.verify(MESSAGE, signature, dummyPublic)).toBe(false);
|
||||
expect(verifier.verify(new Uint8Array(), signature, signer.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('allows for signing/verification (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('just some test message');
|
||||
const signer = keyring.createFromUri(PHRASE);
|
||||
const verifier = keyring.addFromJson(
|
||||
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
const signature = signer.sign(MESSAGE, { withType: true });
|
||||
const dummyPublic = verifier.publicKey.slice();
|
||||
|
||||
dummyPublic[dummyPublic.length - 1] = 0;
|
||||
|
||||
expect(verifier.verify(MESSAGE, signature, signer.publicKey)).toBe(true);
|
||||
expect(verifier.verify(MESSAGE, signature, dummyPublic)).toBe(false);
|
||||
expect(verifier.verify(new Uint8Array(), signature, signer.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('raw pair add/create', (): void => {
|
||||
const json = JSON.parse('{"address":"5PjeoaQzCoYbSi42aQRKB3Sx18StCaEAzCbGEEbWbZyfKS3H","encoded":"JQUl8ZpoXv2OMkL9TPylLmcIye2cYhaS9INICbFgZTsAgAAAAQAAAAgAAAAr/0hJOOzokIdBG71TstigLABX9D5xGD7L37ySxtjDrVRg26LL90jLQ47quT9o3bq6ppXMVL6USk7Q4p3WU66bojTFuCDyhpYRhNbUqU6s0rD3S4bhv9lG+pG9vQ4eD5PVQUvxdANmJpYuDg45nrTmsMC5AHGdFGkHW/LHnkmbFid1cvPYkdiBoef5CIEdoly512pxMupVxnJWF1NT","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"name":"hello"}}') as KeyringPair$Json;
|
||||
const decoded = decodePair('1', base64Decode(json.encoded), json.encoding.type);
|
||||
const keyring = new Keyring({ ss58Format: 44 });
|
||||
|
||||
it('creates a pair from a private/public combo', (): void => {
|
||||
const pair = keyring.createFromPair(decoded, json.meta, 'sr25519');
|
||||
|
||||
expect(pair.address).toEqual('5PjeoaQzCoYbSi42aQRKB3Sx18StCaEAzCbGEEbWbZyfKS3H');
|
||||
expect(pair.isLocked).toEqual(false);
|
||||
expect(pair.meta.name).toEqual('hello');
|
||||
});
|
||||
|
||||
it('adds a pair from a private/public combo', (): void => {
|
||||
keyring.addFromPair(decoded, json.meta, 'sr25519');
|
||||
|
||||
const pair = keyring.getPairs()[0];
|
||||
|
||||
expect(pair.address).toEqual('5PjeoaQzCoYbSi42aQRKB3Sx18StCaEAzCbGEEbWbZyfKS3H');
|
||||
expect(pair.isLocked).toEqual(false);
|
||||
expect(pair.meta.name).toEqual('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('util', (): void => {
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42 });
|
||||
});
|
||||
|
||||
it('can re-encode an address to Polkadot live', (): void => {
|
||||
expect(
|
||||
keyring.encodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 0)
|
||||
).toEqual('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5');
|
||||
});
|
||||
|
||||
it('can re-encode an address to keyring default', (): void => {
|
||||
expect(
|
||||
keyring.encodeAddress('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5')
|
||||
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
});
|
||||
});
|
||||
|
||||
describe('version 2 JSON', (): void => {
|
||||
const PAIR = '{"address":"5CczAE5AmGrZ93MeVhha3Ywam7j9dKB7cArnH7gtrXcMFJvu","encoded":"0xee8f236e2ac3217ce689692a4afc612220dc77fddaed0482f8f95136a7c3e034cccfbc495410a6e9b2439904974ed1d207abeca536ff6985ceb78edeeb3dc343e561c184c488101af8811d1331430b4ccf0e96ef507132e5132964e8564232e7100d973c5bee7b231dd0c8ad5273f3501515a422c8d7ed9d20a73c0ed17c98ee4588e54844bb73052dcad81f7a1094613d63c162fec7446c88b1fae70e","encoding":{"content":["pkcs8","sr25519"],"type":"xsalsa20-poly1305","version":"2"},"meta":{"genesisHash":"0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e","name":"json v2","tags":[],"whenCreated":1595243159596}}';
|
||||
const PASS2 = 'versionTwo';
|
||||
const PASS3 = 'versionThree';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42 });
|
||||
});
|
||||
|
||||
it('can decode from a version 2 JSON file', (): void => {
|
||||
const pair = keyring.addFromJson(JSON.parse(PAIR) as KeyringPair$Json);
|
||||
|
||||
pair.decodePkcs8(PASS2);
|
||||
|
||||
const json = pair.toJson(PASS3);
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe('5CczAE5AmGrZ93MeVhha3Ywam7j9dKB7cArnH7gtrXcMFJvu');
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'sr25519'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
|
||||
pair.decodePkcs8(PASS3);
|
||||
|
||||
expect(pair.address).toEqual('5CczAE5AmGrZ93MeVhha3Ywam7j9dKB7cArnH7gtrXcMFJvu');
|
||||
});
|
||||
});
|
||||
|
||||
describe('version 3 JSON (hex)', (): void => {
|
||||
const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"0xcd238963070cc4d6806053ee1ac500c7add9c28732bb5d434a332f84a91d9be0008000000100000008000000cf630a1113941b350ddd06697e50399183162e5e9a0e893eafc7f5f4893a223dca5055706b9925b56fdb4304192143843da718e11717daf89cf4f4781f94fb443f61432f782d54280af9eec90bd3069c3cc2d957a42b7c18dc2e9497f623735518e0e49b58f8e4db2c09da3a45dbb935659d015fc94b946cba75b606a6ff7f4e823f6b049e2e6892026b49de02d6dbbd64646fe0933f537d9ea53a70be","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639}}';
|
||||
const PASS3 = 'version3';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 2 });
|
||||
});
|
||||
|
||||
it('can decode from a version 3 JSON file', (): void => {
|
||||
const pair = keyring.addFromJson(JSON.parse(PAIR) as KeyringPair$Json);
|
||||
|
||||
pair.decodePkcs8(PASS3);
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe('FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('version 3 JSON (base64)', (): void => {
|
||||
const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639,"whenEdited":1595278378596}}';
|
||||
const PASS3 = 'version3';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 2 });
|
||||
});
|
||||
|
||||
it('can decode from a version 3 JSON file', (): void => {
|
||||
const pair = keyring.addFromJson(JSON.parse(PAIR) as KeyringPair$Json);
|
||||
|
||||
pair.decodePkcs8(PASS3);
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe('FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('wordlist', (): void => {
|
||||
it('creates keypair from different wordlists mnemonics', (): void => {
|
||||
Object.keys(languages).forEach((language) => {
|
||||
const mnemonic = mnemonicGenerate(12, languages[language as keyof typeof languages]);
|
||||
const keyring = new Keyring({
|
||||
type: 'ed25519'
|
||||
});
|
||||
|
||||
expect(keyring.addFromMnemonic(
|
||||
mnemonic,
|
||||
{},
|
||||
'ed25519',
|
||||
languages[language as keyof typeof languages]
|
||||
)).toBeDefined();
|
||||
});
|
||||
});
|
||||
it('cannot create from invalid wordlist', (): void => {
|
||||
const mnemonic = mnemonicGenerate(12, languages.japanese);
|
||||
const keyring = new Keyring({
|
||||
type: 'ed25519'
|
||||
});
|
||||
|
||||
expect(() => keyring.addFromMnemonic(
|
||||
mnemonic,
|
||||
{},
|
||||
'ed25519',
|
||||
languages.english
|
||||
)).toThrow('Invalid bip39 mnemonic specified');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import './packageDetect.js';
|
||||
|
||||
import { Keyring } from './bundle.js';
|
||||
|
||||
export * from './bundle.js';
|
||||
|
||||
export default Keyring;
|
||||
@@ -0,0 +1,307 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@pezkuwi/util-crypto/types';
|
||||
import type { KeyringInstance, KeyringOptions, KeyringPair, KeyringPair$Json, KeyringPair$Meta } from './types.js';
|
||||
|
||||
import { hexToU8a, isHex, stringToU8a } from '@pezkuwi/util';
|
||||
import { base64Decode, decodeAddress, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, ethereumEncode, hdEthereum, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { createPair } from './pair/index.js';
|
||||
import { DEV_PHRASE } from './defaults.js';
|
||||
import { Pairs } from './pairs.js';
|
||||
|
||||
const PairFromSeed = {
|
||||
ecdsa: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
|
||||
ed25519: (seed: Uint8Array): Keypair => ed25519FromSeed(seed),
|
||||
ethereum: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
|
||||
sr25519: (seed: Uint8Array): Keypair => sr25519FromSeed(seed)
|
||||
};
|
||||
|
||||
function pairToPublic ({ publicKey }: KeyringPair): Uint8Array {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* # @polkadot/keyring
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* @name Keyring
|
||||
* @summary Keyring management of user accounts
|
||||
* @description Allows generation of keyring pairs from a variety of input combinations, such as
|
||||
* json object containing account address or public key, account metadata, and account encoded using
|
||||
* `addFromJson`, or by providing those values as arguments separately to `addFromAddress`,
|
||||
* or by providing the mnemonic (seed phrase) and account metadata as arguments to `addFromMnemonic`.
|
||||
* Stores the keyring pairs in a keyring pair dictionary. Removal of the keyring pairs from the keyring pair
|
||||
* dictionary is achieved using `removePair`. Retrieval of all the stored pairs via `getPairs` or perform
|
||||
* lookup of a pair for a given account address or public key using `getPair`. JSON metadata associated with
|
||||
* an account may be obtained using `toJson` accompanied by the account passphrase.
|
||||
*/
|
||||
export class Keyring implements KeyringInstance {
|
||||
readonly #pairs: Pairs;
|
||||
|
||||
readonly #type: KeypairType;
|
||||
|
||||
#ss58?: number | undefined;
|
||||
|
||||
public decodeAddress = decodeAddress;
|
||||
|
||||
constructor (options: KeyringOptions = {}) {
|
||||
options.type = options.type || 'ed25519';
|
||||
|
||||
if (!['ecdsa', 'ethereum', 'ed25519', 'sr25519'].includes(options.type || 'undefined')) {
|
||||
throw new Error(`Expected a keyring type of either 'ed25519', 'sr25519', 'ethereum' or 'ecdsa', found '${options.type || 'unknown'}`);
|
||||
}
|
||||
|
||||
this.#pairs = new Pairs();
|
||||
this.#ss58 = options.ss58Format;
|
||||
this.#type = options.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description retrieve the pairs (alias for getPairs)
|
||||
*/
|
||||
public get pairs (): KeyringPair[] {
|
||||
return this.getPairs();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description retrieve the publicKeys (alias for getPublicKeys)
|
||||
*/
|
||||
public get publicKeys (): Uint8Array[] {
|
||||
return this.getPublicKeys();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the type of the keyring, ed25519, sr25519 or ecdsa
|
||||
*/
|
||||
public get type (): KeypairType {
|
||||
return this.#type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addPair
|
||||
* @summary Stores an account, given a keyring pair, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||
*/
|
||||
public addPair (pair: KeyringPair): KeyringPair {
|
||||
return this.#pairs.add(pair);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addFromAddress
|
||||
* @summary Stores an account, given an account address, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||
* @description Allows user to explicitly provide separate inputs including account address or public key, and optionally
|
||||
* the associated account metadata, and the default encoded value as arguments (that may be obtained from the json file
|
||||
* of an account backup), and then generates a keyring pair from them that it passes to
|
||||
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||
*/
|
||||
public addFromAddress (address: string | Uint8Array, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, type: KeypairType = this.type, ignoreChecksum?: boolean, encType?: EncryptedJsonEncoding[]): KeyringPair {
|
||||
const publicKey = this.decodeAddress(address, ignoreChecksum);
|
||||
|
||||
return this.addPair(createPair({ toSS58: this.encodeAddress, type }, { publicKey, secretKey: new Uint8Array() }, meta, encoded, encType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addFromJson
|
||||
* @summary Stores an account, given JSON data, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||
* @description Allows user to provide a json object argument that contains account information (that may be obtained from the json file
|
||||
* of an account backup), and then generates a keyring pair from it that it passes to
|
||||
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||
*/
|
||||
public addFromJson (json: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair {
|
||||
return this.addPair(this.createFromJson(json, ignoreChecksum));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addFromMnemonic
|
||||
* @summary Stores an account, given a mnemonic, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||
* @description Allows user to provide a mnemonic (seed phrase that is provided when account is originally created)
|
||||
* argument and a metadata argument that contains account information (that may be obtained from the json file
|
||||
* of an account backup), and then generates a keyring pair from it that it passes to
|
||||
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||
*/
|
||||
public addFromMnemonic (mnemonic: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
|
||||
return this.addFromUri(mnemonic, meta, type, wordlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addFromPair
|
||||
* @summary Stores an account created from an explicit publicKey/secreteKey combination
|
||||
*/
|
||||
public addFromPair (pair: Keypair, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||
return this.addPair(
|
||||
this.createFromPair(pair, meta, type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addFromSeed
|
||||
* @summary Stores an account, given seed data, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
||||
* @description Stores in a keyring pair dictionary the public key of the pair as a key and the pair as the associated value.
|
||||
* Allows user to provide the account seed as an argument, and then generates a keyring pair from it that it passes to
|
||||
* `addPair` to store in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||
*/
|
||||
public addFromSeed (seed: Uint8Array, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||
return this.addPair(
|
||||
createPair({ toSS58: this.encodeAddress, type }, PairFromSeed[type](seed), meta, null)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name addFromUri
|
||||
* @summary Creates an account via an suri
|
||||
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed). The secret can be a hex string, mnemonic phrase or a string (to be padded)
|
||||
*/
|
||||
public addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
|
||||
return this.addPair(
|
||||
this.createFromUri(suri, meta, type, wordlist)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name createFromJson
|
||||
* @description Creates a pair from a JSON keyfile
|
||||
*/
|
||||
public createFromJson ({ address, encoded, encoding: { content, type, version }, meta }: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair {
|
||||
if (version === '3' && content[0] !== 'pkcs8') {
|
||||
throw new Error(`Unable to decode non-pkcs8 type, [${content.join(',')}] found}`);
|
||||
}
|
||||
|
||||
const cryptoType = version === '0' || !Array.isArray(content)
|
||||
? this.type
|
||||
: content[1];
|
||||
const encType = !Array.isArray(type)
|
||||
? [type]
|
||||
: type;
|
||||
|
||||
if (!['ed25519', 'sr25519', 'ecdsa', 'ethereum'].includes(cryptoType)) {
|
||||
throw new Error(`Unknown crypto type ${cryptoType}`);
|
||||
}
|
||||
|
||||
// Here the address and publicKey are 32 bytes and isomorphic. This is why the address field needs to be the public key for ethereum type pairs
|
||||
const publicKey = isHex(address)
|
||||
? hexToU8a(address)
|
||||
: this.decodeAddress(address, ignoreChecksum);
|
||||
const decoded = isHex(encoded)
|
||||
? hexToU8a(encoded)
|
||||
: base64Decode(encoded);
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type: cryptoType as KeypairType }, { publicKey, secretKey: new Uint8Array() }, meta, decoded, encType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name createFromPair
|
||||
* @summary Creates a pair from an explicit publicKey/secreteKey combination
|
||||
*/
|
||||
public createFromPair (pair: Keypair, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||
return createPair({ toSS58: this.encodeAddress, type }, pair, meta, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name createFromUri
|
||||
* @summary Creates a Keypair from an suri
|
||||
* @description This creates a pair from the suri, but does not add it to the keyring
|
||||
*/
|
||||
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
|
||||
// here we only aut-add the dev phrase if we have a hard-derived path
|
||||
const suri = _suri.startsWith('//')
|
||||
? `${DEV_PHRASE}${_suri}`
|
||||
: _suri;
|
||||
const { derivePath, password, path, phrase } = keyExtractSuri(suri);
|
||||
let seed: Uint8Array;
|
||||
const isPhraseHex = isHex(phrase, 256);
|
||||
|
||||
if (isPhraseHex) {
|
||||
seed = hexToU8a(phrase);
|
||||
} else {
|
||||
const parts = phrase.split(' ');
|
||||
|
||||
if ([12, 15, 18, 21, 24].includes(parts.length)) {
|
||||
seed = type === 'ethereum'
|
||||
? mnemonicToLegacySeed(phrase, '', false, 64)
|
||||
: mnemonicToMiniSecret(phrase, password, wordlist);
|
||||
} else {
|
||||
if (phrase.length > 32) {
|
||||
throw new Error('specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');
|
||||
}
|
||||
|
||||
seed = stringToU8a(phrase.padEnd(32));
|
||||
}
|
||||
}
|
||||
|
||||
const derived = type === 'ethereum'
|
||||
? isPhraseHex
|
||||
? PairFromSeed[type](seed) // for eth, if the private key is provided as suri, it must be derived only once
|
||||
: hdEthereum(seed, derivePath.substring(1))
|
||||
: keyFromPath(PairFromSeed[type](seed), path, type);
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type }, derived, meta, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name encodeAddress
|
||||
* @description Encodes the input into an ss58 representation
|
||||
*/
|
||||
public encodeAddress = (address: Uint8Array | string, ss58Format?: number): string => {
|
||||
return this.type === 'ethereum'
|
||||
? ethereumEncode(address)
|
||||
: encodeAddress(address, ss58Format ?? this.#ss58);
|
||||
};
|
||||
|
||||
/**
|
||||
* @name getPair
|
||||
* @summary Retrieves an account keyring pair from the Keyring Pair Dictionary, given an account address
|
||||
* @description Returns a keyring pair value from the keyring pair dictionary by performing
|
||||
* a key lookup using the provided account address or public key (after decoding it).
|
||||
*/
|
||||
public getPair (address: string | Uint8Array): KeyringPair {
|
||||
return this.#pairs.get(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getPairs
|
||||
* @summary Retrieves all account keyring pairs from the Keyring Pair Dictionary
|
||||
* @description Returns an array list of all the keyring pair values that are stored in the keyring pair dictionary.
|
||||
*/
|
||||
public getPairs (): KeyringPair[] {
|
||||
return this.#pairs.all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getPublicKeys
|
||||
* @summary Retrieves Public Keys of all Keyring Pairs stored in the Keyring Pair Dictionary
|
||||
* @description Returns an array list of all the public keys associated with each of the keyring pair values that are stored in the keyring pair dictionary.
|
||||
*/
|
||||
public getPublicKeys (): Uint8Array[] {
|
||||
return this.#pairs.all().map(pairToPublic);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name removePair
|
||||
* @description Deletes the provided input address or public key from the stored Keyring Pair Dictionary.
|
||||
*/
|
||||
public removePair (address: string | Uint8Array): void {
|
||||
this.#pairs.remove(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name setSS58Format;
|
||||
* @description Sets the ss58 format for the keyring
|
||||
*/
|
||||
public setSS58Format (ss58: number): void {
|
||||
this.#ss58 = ss58;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name toJson
|
||||
* @summary Returns a JSON object associated with the input argument that contains metadata assocated with an account
|
||||
* @description Returns a JSON object containing the metadata associated with an account
|
||||
* when valid address or public key and when the account passphrase is provided if the account secret
|
||||
* is not already unlocked and available in memory. Note that in [Polkadot-JS Apps](https://github.com/polkadot-js/apps) the user
|
||||
* may backup their account to a JSON file that contains this information.
|
||||
*/
|
||||
public toJson (address: string | Uint8Array, passphrase?: string): KeyringPair$Json {
|
||||
return this.#pairs.get(address).toJson(passphrase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './index.js';
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
// (packageInfo imports will be kept as-is, user-editable)
|
||||
|
||||
import { detectPackage } from '@pezkuwi/util';
|
||||
import { packageInfo as utilInfo } from '@pezkuwi/util/packageInfo';
|
||||
import { packageInfo as cryptoInfo } from '@pezkuwi/util-crypto/packageInfo';
|
||||
|
||||
import { packageInfo } from './packageInfo.js';
|
||||
|
||||
detectPackage(packageInfo, null, [cryptoInfo, utilInfo]);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/keyring', path: 'auto', type: 'auto', version: '14.0.1' };
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
describe('decode', (): void => {
|
||||
it('fails when no data provided', (): void => {
|
||||
expect(
|
||||
(): void => keyring.alice.decodePkcs8()
|
||||
).toThrow(/No encrypted data available/);
|
||||
});
|
||||
|
||||
it('returns correct publicKey from encoded', (): void => {
|
||||
const PASS = 'testing';
|
||||
|
||||
expect(
|
||||
(): void => keyring.alice.decodePkcs8(
|
||||
PASS, keyring.alice.encodePkcs8(PASS)
|
||||
)
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { EncryptedJsonEncoding } from '@pezkuwi/util-crypto/types';
|
||||
|
||||
import { u8aEq } from '@pezkuwi/util';
|
||||
import { jsonDecryptData } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { PAIR_DIV, PAIR_HDR, PUB_LENGTH, SEC_LENGTH, SEED_LENGTH } from './defaults.js';
|
||||
|
||||
const SEED_OFFSET = PAIR_HDR.length;
|
||||
|
||||
/**
|
||||
* Decode a pair, taking into account the generation-specific formats and headers
|
||||
*
|
||||
* For divisor/headers, don't rely on the magic being static. These will
|
||||
* change between generations, aka with the long-awaited 4th generation
|
||||
* of the format. The external decode interface is the only way to use and decode these.
|
||||
**/
|
||||
export function decodePair (passphrase?: string, encrypted?: Uint8Array | null, _encType?: EncryptedJsonEncoding | EncryptedJsonEncoding[]): { publicKey: Uint8Array; secretKey: Uint8Array } {
|
||||
const encType = Array.isArray(_encType) || _encType === undefined
|
||||
? _encType
|
||||
: [_encType];
|
||||
const decrypted = jsonDecryptData(encrypted, passphrase, encType);
|
||||
const header = decrypted.subarray(0, PAIR_HDR.length);
|
||||
|
||||
// check the start header (generations 1-3)
|
||||
if (!u8aEq(header, PAIR_HDR)) {
|
||||
throw new Error('Invalid encoding header found in body');
|
||||
}
|
||||
|
||||
// setup for generation 3 format
|
||||
let secretKey = decrypted.subarray(SEED_OFFSET, SEED_OFFSET + SEC_LENGTH);
|
||||
let divOffset = SEED_OFFSET + SEC_LENGTH;
|
||||
let divider = decrypted.subarray(divOffset, divOffset + PAIR_DIV.length);
|
||||
|
||||
// old-style (generation 1 & 2), we have the seed here
|
||||
if (!u8aEq(divider, PAIR_DIV)) {
|
||||
divOffset = SEED_OFFSET + SEED_LENGTH;
|
||||
secretKey = decrypted.subarray(SEED_OFFSET, divOffset);
|
||||
divider = decrypted.subarray(divOffset, divOffset + PAIR_DIV.length);
|
||||
|
||||
// check the divisior at this point (already checked for generation 3)
|
||||
if (!u8aEq(divider, PAIR_DIV)) {
|
||||
throw new Error('Invalid encoding divider found in body');
|
||||
}
|
||||
}
|
||||
|
||||
const pubOffset = divOffset + PAIR_DIV.length;
|
||||
const publicKey = decrypted.subarray(pubOffset, pubOffset + PUB_LENGTH);
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
secretKey
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/** public/secret section divider (generation 1-3, will change in 4, don't rely on value) */
|
||||
export const PAIR_DIV = new Uint8Array([161, 35, 3, 33, 0]);
|
||||
|
||||
/** public/secret start block (generation 1-3, will change in 4, don't rely on value) */
|
||||
export const PAIR_HDR = new Uint8Array([48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
|
||||
|
||||
/** length of a public key */
|
||||
export const PUB_LENGTH = 32;
|
||||
|
||||
/** length of a salt */
|
||||
export const SALT_LENGTH = 32;
|
||||
|
||||
/** length of a secret key */
|
||||
export const SEC_LENGTH = 64;
|
||||
|
||||
/** length of a user-input seed */
|
||||
export const SEED_LENGTH = 32;
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { NONCE_LENGTH, SCRYPT_LENGTH } from '@pezkuwi/util-crypto/json/constants';
|
||||
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
import { PAIR_DIV, PAIR_HDR, PUB_LENGTH, SEC_LENGTH } from './defaults.js';
|
||||
|
||||
const DECODED_LENGTH = PAIR_DIV.length + PAIR_HDR.length + PUB_LENGTH + SEC_LENGTH;
|
||||
const ENCODED_LENGTH = 16 + DECODED_LENGTH + NONCE_LENGTH + SCRYPT_LENGTH;
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
describe('encode', (): void => {
|
||||
it('returns PKCS8 when no passphrase supplied', (): void => {
|
||||
expect(
|
||||
keyring.alice.encodePkcs8()
|
||||
).toHaveLength(DECODED_LENGTH);
|
||||
});
|
||||
|
||||
it('returns encoded PKCS8 when passphrase supplied', (): void => {
|
||||
expect(
|
||||
keyring.alice.encodePkcs8('testing')
|
||||
).toHaveLength(ENCODED_LENGTH);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PairInfo } from './types.js';
|
||||
|
||||
import { u8aConcat } from '@pezkuwi/util';
|
||||
import { naclEncrypt, scryptEncode, scryptToU8a } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { PAIR_DIV, PAIR_HDR } from './defaults.js';
|
||||
|
||||
/**
|
||||
* Encode a pair with the latest generation format (generation 3)
|
||||
**/
|
||||
export function encodePair ({ publicKey, secretKey }: PairInfo, passphrase?: string): Uint8Array {
|
||||
if (!secretKey) {
|
||||
throw new Error('Expected a valid secretKey to be passed to encode');
|
||||
}
|
||||
|
||||
const encoded = u8aConcat(PAIR_HDR, secretKey, PAIR_DIV, publicKey);
|
||||
|
||||
if (!passphrase) {
|
||||
return encoded;
|
||||
}
|
||||
|
||||
// this is only for generation 3 (previous generations are only handled in decoding)
|
||||
const { params, password, salt } = scryptEncode(passphrase);
|
||||
const { encrypted, nonce } = naclEncrypt(encoded, password.subarray(0, 32));
|
||||
|
||||
return u8aConcat(scryptToU8a(salt, params), nonce, encrypted);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a, u8aToHex } from '@pezkuwi/util';
|
||||
import { cryptoWaitReady, encodeAddress as toSS58, setSS58Format } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { PAIRSSR25519 } from '../testing.js';
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
import { createPair } from './index.js';
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
const TEST_ADDRESS = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('pair', (): void => {
|
||||
const SIGNATURE = new Uint8Array([80, 191, 198, 147, 225, 207, 75, 88, 126, 39, 129, 109, 191, 38, 72, 181, 75, 254, 81, 143, 244, 79, 237, 38, 236, 141, 28, 252, 134, 26, 169, 234, 79, 33, 153, 158, 151, 34, 175, 188, 235, 20, 35, 135, 83, 120, 139, 211, 233, 130, 1, 208, 201, 215, 73, 80, 56, 98, 185, 196, 11, 8, 193, 14]);
|
||||
|
||||
it('has a publicKey', (): void => {
|
||||
expect(
|
||||
keyring.alice.publicKey
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
expect(
|
||||
keyring.alice.addressRaw
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
});
|
||||
|
||||
it('allows signing', (): void => {
|
||||
expect(
|
||||
keyring.alice.sign(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64])
|
||||
)
|
||||
).toEqual(SIGNATURE);
|
||||
});
|
||||
|
||||
it('validates a correctly signed message', (): void => {
|
||||
expect(
|
||||
keyring.alice.verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
SIGNATURE,
|
||||
keyring.alice.publicKey
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('fails a correctly signed message (signer changed)', (): void => {
|
||||
expect(
|
||||
keyring.alice.verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
SIGNATURE,
|
||||
keyring.bob.publicKey
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('fails a correctly signed message (message changed)', (): void => {
|
||||
expect(
|
||||
keyring.alice.verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]),
|
||||
SIGNATURE,
|
||||
keyring.alice.publicKey
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('allows vrf sign and verify', (): void => {
|
||||
const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]);
|
||||
|
||||
expect(
|
||||
keyring.alice.vrfVerify(
|
||||
message,
|
||||
keyring.alice.vrfSign(message),
|
||||
keyring.alice.publicKey
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('fails vrf sign and verify (publicKey changed)', (): void => {
|
||||
const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]);
|
||||
|
||||
expect(
|
||||
keyring.alice.vrfVerify(
|
||||
message,
|
||||
keyring.alice.vrfSign(message),
|
||||
keyring.bob.publicKey
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('allows setting/getting of meta', (): void => {
|
||||
keyring.bob.setMeta({ foo: 'bar', something: 'else' });
|
||||
|
||||
expect(keyring.bob.meta).toMatchObject({ foo: 'bar', something: 'else' });
|
||||
|
||||
keyring.bob.setMeta({ something: 'thing' });
|
||||
|
||||
expect(keyring.bob.meta).toMatchObject({ foo: 'bar', something: 'thing' });
|
||||
});
|
||||
|
||||
it('allows encoding of address with different prefixes', (): void => {
|
||||
expect(keyring.alice.address).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');
|
||||
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
setSS58Format(255);
|
||||
|
||||
expect(keyring.alice.address).toEqual('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k');
|
||||
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
setSS58Format(42);
|
||||
});
|
||||
|
||||
it('allows getting public key after decoding', (): void => {
|
||||
const PASS = 'testing';
|
||||
const encoded = keyring.alice.encodePkcs8(PASS);
|
||||
|
||||
const pair = createPair({ toSS58, type: 'sr25519' }, { publicKey: keyring.alice.publicKey });
|
||||
|
||||
pair.decodePkcs8(PASS, encoded);
|
||||
|
||||
expect(pair.isLocked).toEqual(false);
|
||||
});
|
||||
|
||||
it('allows derivation on the pair', (): void => {
|
||||
const alice = createPair({ toSS58, type: 'sr25519' }, { publicKey: hexToU8a(PAIRSSR25519[0].p), secretKey: hexToU8a(PAIRSSR25519[0].s) }, {});
|
||||
const stash = alice.derive('//stash');
|
||||
const soft = alice.derive('//funding/0');
|
||||
|
||||
expect(stash.publicKey).toEqual(hexToU8a(PAIRSSR25519[1].p));
|
||||
expect(soft.address).toEqual('5ECQNn7UueWHPFda5qUi4fTmTtyCnPvGnuoyVVSj5CboJh9J');
|
||||
});
|
||||
|
||||
it('fails to sign when locked', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'sr25519' }, { publicKey: keyring.alice.publicKey });
|
||||
|
||||
expect(pair.isLocked).toEqual(true);
|
||||
expect((): Uint8Array =>
|
||||
pair.sign(new Uint8Array([0]))
|
||||
).toThrow('Cannot sign with a locked key pair');
|
||||
});
|
||||
|
||||
describe('ethereum', (): void => {
|
||||
const PUBLICDERIVED = new Uint8Array([
|
||||
3, 129, 53, 27, 27, 70, 210, 96,
|
||||
43, 9, 146, 187, 93, 85, 49, 249,
|
||||
193, 105, 107, 8, 18, 254, 178, 83,
|
||||
75, 104, 132, 173, 196, 126, 46, 29,
|
||||
139
|
||||
]);
|
||||
const SECRETDERIVED = new Uint8Array([
|
||||
7, 13, 195, 17, 115, 0, 1, 25,
|
||||
24, 226, 107, 2, 23, 105, 69, 204,
|
||||
21, 195, 213, 72, 207, 73, 253, 132,
|
||||
24, 217, 127, 147, 175, 105, 158, 70
|
||||
]);
|
||||
|
||||
it('has a valid address from a known public', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077') });
|
||||
|
||||
expect(pair.address).toEqual(TEST_ADDRESS);
|
||||
expect(pair.addressRaw).toEqual(hexToU8a(TEST_ADDRESS));
|
||||
});
|
||||
|
||||
it('has a valid address from a known ethereum address (20 length)', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: new Uint8Array([75, 32, 205, 127, 248, 119, 52, 31, 46, 171, 170, 23, 158, 23, 46, 108, 95, 180, 186, 168]), secretKey: new Uint8Array([]) });
|
||||
|
||||
expect(pair.address.toLowerCase()).toEqual('0x4b20cd7ff877341f2eabaa179e172e6c5fb4baa8');
|
||||
expect(pair.addressRaw).toEqual(hexToU8a('0x4b20cd7ff877341f2eabaa179e172e6c5fb4baa8'));
|
||||
});
|
||||
|
||||
it('converts to json', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: PUBLICDERIVED, secretKey: SECRETDERIVED });
|
||||
const json = pair.toJson('password');
|
||||
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'ethereum'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
expect(json.address).toEqual(u8aToHex(PUBLICDERIVED));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@pezkuwi/util-crypto/types';
|
||||
import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta, SignOptions } from '../types.js';
|
||||
import type { PairInfo } from './types.js';
|
||||
|
||||
import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToHex, u8aToU8a } from '@pezkuwi/util';
|
||||
import { blake2AsU8a, ed25519PairFromSeed as ed25519FromSeed, ed25519Sign, ethereumEncode, keccakAsU8a, keyExtractPath, keyFromPath, secp256k1Compress, secp256k1Expand, secp256k1PairFromSeed as secp256k1FromSeed, secp256k1Sign, signatureVerify, sr25519PairFromSeed as sr25519FromSeed, sr25519Sign, sr25519VrfSign, sr25519VrfVerify } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { decodePair } from './decode.js';
|
||||
import { encodePair } from './encode.js';
|
||||
import { pairToJson } from './toJson.js';
|
||||
|
||||
interface Setup {
|
||||
toSS58: (publicKey: Uint8Array) => string;
|
||||
type: KeypairType;
|
||||
}
|
||||
|
||||
const SIG_TYPE_NONE = new Uint8Array();
|
||||
|
||||
const TYPE_FROM_SEED = {
|
||||
ecdsa: secp256k1FromSeed,
|
||||
ed25519: ed25519FromSeed,
|
||||
ethereum: secp256k1FromSeed,
|
||||
sr25519: sr25519FromSeed
|
||||
};
|
||||
|
||||
const TYPE_PREFIX = {
|
||||
ecdsa: new Uint8Array([2]),
|
||||
ed25519: new Uint8Array([0]),
|
||||
ethereum: new Uint8Array([2]),
|
||||
sr25519: new Uint8Array([1])
|
||||
};
|
||||
|
||||
const TYPE_SIGNATURE = {
|
||||
ecdsa: (m: Uint8Array, p: Partial<Keypair>) => secp256k1Sign(m, p, 'blake2'),
|
||||
ed25519: ed25519Sign,
|
||||
ethereum: (m: Uint8Array, p: Partial<Keypair>) => secp256k1Sign(m, p, 'keccak'),
|
||||
sr25519: sr25519Sign
|
||||
};
|
||||
|
||||
const TYPE_ADDRESS = {
|
||||
ecdsa: (p: Uint8Array) => p.length > 32 ? blake2AsU8a(p) : p,
|
||||
ed25519: (p: Uint8Array) => p,
|
||||
ethereum: (p: Uint8Array) => p.length === 20 ? p : keccakAsU8a(secp256k1Expand(p)),
|
||||
sr25519: (p: Uint8Array) => p
|
||||
};
|
||||
|
||||
function isLocked (secretKey?: Uint8Array): secretKey is undefined {
|
||||
return !secretKey || u8aEmpty(secretKey);
|
||||
}
|
||||
|
||||
function vrfHash (proof: Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): Uint8Array {
|
||||
return blake2AsU8a(u8aConcat(context || '', extra || '', proof));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name createPair
|
||||
* @summary Creates a keyring pair object
|
||||
* @description Creates a keyring pair object with provided account public key, metadata, and encoded arguments.
|
||||
* The keyring pair stores the account state including the encoded address and associated metadata.
|
||||
*
|
||||
* It has properties whose values are functions that may be called to perform account actions:
|
||||
*
|
||||
* - `address` function retrieves the address associated with the account.
|
||||
* - `decodedPkcs8` function is called with the account passphrase and account encoded public key.
|
||||
* It decodes the encoded public key using the passphrase provided to obtain the decoded account public key
|
||||
* and associated secret key that are then available in memory, and changes the account address stored in the
|
||||
* state of the pair to correspond to the address of the decoded public key.
|
||||
* - `encodePkcs8` function when provided with the correct passphrase associated with the account pair
|
||||
* and when the secret key is in memory (when the account pair is not locked) it returns an encoded
|
||||
* public key of the account.
|
||||
* - `meta` is the metadata that is stored in the state of the pair, either when it was originally
|
||||
* created or set via `setMeta`.
|
||||
* - `publicKey` returns the public key stored in memory for the pair.
|
||||
* - `sign` may be used to return a signature by signing a provided message with the secret
|
||||
* key (if it is in memory) using Nacl.
|
||||
* - `toJson` calls another `toJson` function and provides the state of the pair,
|
||||
* it generates arguments to be passed to the other `toJson` function including an encoded public key of the account
|
||||
* that it generates using the secret key from memory (if it has been made available in memory)
|
||||
* and the optionally provided passphrase argument. It passes a third boolean argument to `toJson`
|
||||
* indicating whether the public key has been encoded or not (if a passphrase argument was provided then it is encoded).
|
||||
* The `toJson` function that it calls returns a JSON object with properties including the `address`
|
||||
* and `meta` that are assigned with the values stored in the corresponding state variables of the account pair,
|
||||
* an `encoded` property that is assigned with the encoded public key in hex format, and an `encoding`
|
||||
* property that indicates whether the public key value of the `encoded` property is encoded or not.
|
||||
*/
|
||||
export function createPair ({ toSS58, type }: Setup, { publicKey, secretKey }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, encTypes?: EncryptedJsonEncoding[]): KeyringPair {
|
||||
const decodePkcs8 = (passphrase?: string, userEncoded?: Uint8Array | null): void => {
|
||||
const decoded = decodePair(passphrase, userEncoded || encoded, encTypes);
|
||||
|
||||
if (decoded.secretKey.length === 64) {
|
||||
publicKey = decoded.publicKey;
|
||||
secretKey = decoded.secretKey;
|
||||
} else {
|
||||
const pair = TYPE_FROM_SEED[type](decoded.secretKey);
|
||||
|
||||
publicKey = pair.publicKey;
|
||||
secretKey = pair.secretKey;
|
||||
}
|
||||
};
|
||||
|
||||
const recode = (passphrase?: string): Uint8Array => {
|
||||
isLocked(secretKey) && encoded && decodePkcs8(passphrase, encoded);
|
||||
|
||||
encoded = encodePair({ publicKey, secretKey }, passphrase); // re-encode, latest version
|
||||
encTypes = undefined; // swap to defaults, latest version follows
|
||||
|
||||
return encoded;
|
||||
};
|
||||
|
||||
const encodeAddress = (): string => {
|
||||
const raw = TYPE_ADDRESS[type](publicKey);
|
||||
|
||||
return type === 'ethereum'
|
||||
? ethereumEncode(raw)
|
||||
: toSS58(raw);
|
||||
};
|
||||
|
||||
return {
|
||||
get address (): string {
|
||||
return encodeAddress();
|
||||
},
|
||||
get addressRaw (): Uint8Array {
|
||||
const raw = TYPE_ADDRESS[type](publicKey);
|
||||
|
||||
return type === 'ethereum'
|
||||
? raw.slice(-20)
|
||||
: raw;
|
||||
},
|
||||
get isLocked (): boolean {
|
||||
return isLocked(secretKey);
|
||||
},
|
||||
get meta (): KeyringPair$Meta {
|
||||
return meta;
|
||||
},
|
||||
get publicKey (): Uint8Array {
|
||||
return publicKey;
|
||||
},
|
||||
get type (): KeypairType {
|
||||
return type;
|
||||
},
|
||||
// eslint-disable-next-line sort-keys
|
||||
decodePkcs8,
|
||||
derive: (suri: string, meta?: KeyringPair$Meta): KeyringPair => {
|
||||
if (type === 'ethereum') {
|
||||
throw new Error('Unable to derive on this keypair');
|
||||
} else if (isLocked(secretKey)) {
|
||||
throw new Error('Cannot derive on a locked keypair');
|
||||
}
|
||||
|
||||
const { path } = keyExtractPath(suri);
|
||||
const derived = keyFromPath({ publicKey, secretKey }, path, type);
|
||||
|
||||
return createPair({ toSS58, type }, derived, meta, null);
|
||||
},
|
||||
encodePkcs8: (passphrase?: string): Uint8Array => {
|
||||
return recode(passphrase);
|
||||
},
|
||||
lock: (): void => {
|
||||
secretKey = new Uint8Array();
|
||||
},
|
||||
setMeta: (additional: KeyringPair$Meta): void => {
|
||||
meta = objectSpread({}, meta, additional);
|
||||
},
|
||||
sign: (message: string | Uint8Array, options: SignOptions = {}): Uint8Array => {
|
||||
if (isLocked(secretKey)) {
|
||||
throw new Error('Cannot sign with a locked key pair');
|
||||
}
|
||||
|
||||
return u8aConcat(
|
||||
options.withType
|
||||
? TYPE_PREFIX[type]
|
||||
: SIG_TYPE_NONE,
|
||||
TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey })
|
||||
);
|
||||
},
|
||||
toJson: (passphrase?: string): KeyringPair$Json => {
|
||||
// NOTE: For ecdsa and ethereum, the publicKey cannot be extracted from the address. For these
|
||||
// pass the hex-encoded publicKey through to the address portion of the JSON (before decoding)
|
||||
// unless the publicKey is already an address
|
||||
const address = ['ecdsa', 'ethereum'].includes(type)
|
||||
? publicKey.length === 20
|
||||
? u8aToHex(publicKey)
|
||||
: u8aToHex(secp256k1Compress(publicKey))
|
||||
: encodeAddress();
|
||||
|
||||
return pairToJson(type, { address, meta }, recode(passphrase), !!passphrase);
|
||||
},
|
||||
unlock: (passphrase?: string): void => {
|
||||
return decodePkcs8(passphrase);
|
||||
},
|
||||
verify: (message: string | Uint8Array, signature: string | Uint8Array, signerPublic: string | Uint8Array): boolean => {
|
||||
return signatureVerify(message, signature, TYPE_ADDRESS[type](u8aToU8a(signerPublic))).isValid;
|
||||
},
|
||||
vrfSign: (message: string | Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): Uint8Array => {
|
||||
if (isLocked(secretKey)) {
|
||||
throw new Error('Cannot sign with a locked key pair');
|
||||
}
|
||||
|
||||
if (type === 'sr25519') {
|
||||
return sr25519VrfSign(message, { secretKey }, context, extra);
|
||||
}
|
||||
|
||||
const proof = TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey });
|
||||
|
||||
return u8aConcat(vrfHash(proof, context, extra), proof);
|
||||
},
|
||||
vrfVerify: (message: string | Uint8Array, vrfResult: Uint8Array, signerPublic: Uint8Array | string, context?: string | Uint8Array, extra?: string | Uint8Array): boolean => {
|
||||
if (type === 'sr25519') {
|
||||
return sr25519VrfVerify(message, vrfResult, publicKey, context, extra);
|
||||
}
|
||||
|
||||
const result = signatureVerify(message, u8aConcat(TYPE_PREFIX[type], vrfResult.subarray(32)), TYPE_ADDRESS[type](u8aToU8a(signerPublic)));
|
||||
|
||||
return result.isValid && u8aEq(vrfResult.subarray(0, 32), vrfHash(vrfResult.subarray(32), context, extra));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '../types.js';
|
||||
|
||||
// empty publicKey
|
||||
const publicKey = new Uint8Array(32);
|
||||
|
||||
// pre-computed via encodeAddress(publicKey)
|
||||
const address = '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM';
|
||||
|
||||
const meta = {
|
||||
isTesting: true,
|
||||
name: 'nobody'
|
||||
};
|
||||
|
||||
const json: KeyringPair$Json = {
|
||||
address,
|
||||
encoded: '',
|
||||
encoding: {
|
||||
content: ['pkcs8', 'ed25519'],
|
||||
type: 'none',
|
||||
version: '0'
|
||||
},
|
||||
meta
|
||||
};
|
||||
|
||||
const pair: KeyringPair = {
|
||||
address,
|
||||
addressRaw: publicKey,
|
||||
decodePkcs8: (_passphrase?: string, _encoded?: Uint8Array): void =>
|
||||
undefined,
|
||||
derive: (_suri: string, _meta?: KeyringPair$Meta): KeyringPair =>
|
||||
pair,
|
||||
encodePkcs8: (_passphrase?: string): Uint8Array =>
|
||||
new Uint8Array(0),
|
||||
isLocked: true,
|
||||
lock: (): void => {
|
||||
// no locking, it is always locked
|
||||
},
|
||||
meta,
|
||||
publicKey,
|
||||
setMeta: (_meta: KeyringPair$Meta): void =>
|
||||
undefined,
|
||||
sign: (_message: Uint8Array): Uint8Array =>
|
||||
new Uint8Array(64),
|
||||
toJson: (_passphrase?: string): KeyringPair$Json =>
|
||||
json,
|
||||
type: 'ed25519',
|
||||
unlock: (_passphrase?: string): void =>
|
||||
undefined,
|
||||
verify: (_message: Uint8Array, _signature: Uint8Array): boolean =>
|
||||
false,
|
||||
vrfSign: (_message: Uint8Array, _context?: string | Uint8Array, _extra?: string | Uint8Array): Uint8Array =>
|
||||
new Uint8Array(96),
|
||||
vrfVerify: (_message: Uint8Array, _vrfResult: Uint8Array, _context?: string | Uint8Array, _extra?: string | Uint8Array): boolean =>
|
||||
false
|
||||
};
|
||||
|
||||
export function nobody (): KeyringPair {
|
||||
return pair;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
describe('toJson', (): void => {
|
||||
it('creates an unencoded output with no passphrase', (): void => {
|
||||
expect(
|
||||
keyring.alice.toJson()
|
||||
).toMatchObject({
|
||||
address: '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua',
|
||||
encoded: 'MFMCAQEwBQYDK2VwBCIEIEFsaWNlICAgICAgICAgICAgICAgICAgICAgICAgICAg0XKnTNpMhlkSwyugqApXrmmrrkEOXMtZ3uhOL0Qy20+hIwMhANFyp0zaTIZZEsMroKgKV65pq65BDlzLWd7oTi9EMttP',
|
||||
encoding: {
|
||||
content: ['pkcs8', 'ed25519'],
|
||||
type: ['none'],
|
||||
version: '3'
|
||||
},
|
||||
meta: {
|
||||
isTesting: true,
|
||||
name: 'alice'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an encoded output with passphrase', (): void => {
|
||||
const json = keyring.alice.toJson('testing');
|
||||
|
||||
expect(json.encoded).toHaveLength(268);
|
||||
expect(json).toMatchObject({
|
||||
address: '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua',
|
||||
encoding: {
|
||||
content: ['pkcs8', 'ed25519'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeypairType } from '@pezkuwi/util-crypto/types';
|
||||
import type { KeyringPair$Json, KeyringPair$Meta } from '../types.js';
|
||||
|
||||
import { objectSpread } from '@pezkuwi/util';
|
||||
import { jsonEncryptFormat } from '@pezkuwi/util-crypto';
|
||||
|
||||
interface PairStateJson {
|
||||
address: string;
|
||||
meta: KeyringPair$Meta;
|
||||
}
|
||||
|
||||
export function pairToJson (type: KeypairType, { address, meta }: PairStateJson, encoded: Uint8Array, isEncrypted: boolean): KeyringPair$Json {
|
||||
return objectSpread(jsonEncryptFormat(encoded, ['pkcs8', type], isEncrypted), {
|
||||
address,
|
||||
meta
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export interface PairInfo {
|
||||
publicKey: Uint8Array;
|
||||
secretKey?: Uint8Array | undefined;
|
||||
seed?: Uint8Array | null;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
import { cryptoWaitReady, ed25519PairFromSeed, encodeAddress as toSS58, randomAsU8a, secp256k1PairFromSeed, sr25519PairFromSeed } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { createPair } from './index.js';
|
||||
|
||||
const MESSAGE = 'this is a test message';
|
||||
const CONTEXT = 'some context';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
const ecdsa = createPair({ toSS58, type: 'ecdsa' }, secp256k1PairFromSeed(randomAsU8a()));
|
||||
const ed25519 = createPair({ toSS58, type: 'ed25519' }, ed25519PairFromSeed(randomAsU8a()));
|
||||
const sr25519 = createPair({ toSS58, type: 'sr25519' }, sr25519PairFromSeed(randomAsU8a()));
|
||||
|
||||
describe('vrf', (): void => {
|
||||
it('has deterministic signature values for ecdsa', (): void => {
|
||||
const sig1 = ecdsa.vrfSign(MESSAGE, CONTEXT);
|
||||
const sig2 = ecdsa.vrfSign(MESSAGE, CONTEXT);
|
||||
|
||||
expect(u8aToHex(sig1)).toEqual(u8aToHex(sig2));
|
||||
expect(ecdsa.vrfVerify(MESSAGE, sig1, ecdsa.publicKey, CONTEXT)).toEqual(true);
|
||||
expect(ecdsa.vrfVerify(MESSAGE, sig2, ecdsa.publicKey, CONTEXT)).toEqual(true);
|
||||
});
|
||||
|
||||
it('has deterministic signature values for ed25519', (): void => {
|
||||
const sig1 = ed25519.vrfSign(MESSAGE, CONTEXT);
|
||||
const sig2 = ed25519.vrfSign(MESSAGE, CONTEXT);
|
||||
|
||||
expect(u8aToHex(sig1)).toEqual(u8aToHex(sig2));
|
||||
expect(ed25519.vrfVerify(MESSAGE, sig1, ed25519.publicKey, CONTEXT)).toEqual(true);
|
||||
expect(ed25519.vrfVerify(MESSAGE, sig2, ed25519.publicKey, CONTEXT)).toEqual(true);
|
||||
});
|
||||
|
||||
it('has deterministic signature values for sr25519', (): void => {
|
||||
const sig1 = sr25519.vrfSign(MESSAGE, CONTEXT);
|
||||
const sig2 = sr25519.vrfSign(MESSAGE, CONTEXT);
|
||||
|
||||
expect(u8aToHex(sig1.slice(0, 32))).toEqual(u8aToHex(sig2.slice(0, 32)));
|
||||
expect(sr25519.vrfVerify(MESSAGE, sig1, sr25519.publicKey, CONTEXT)).toEqual(true);
|
||||
expect(sr25519.vrfVerify(MESSAGE, sig2, sr25519.publicKey, CONTEXT)).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeyringPair, KeyringPairs } from './types.js';
|
||||
|
||||
import { isHex, isU8a, u8aToHex, u8aToU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
type KeyringPairMap = Record<string, KeyringPair>;
|
||||
|
||||
export class Pairs implements KeyringPairs {
|
||||
readonly #map: KeyringPairMap = {};
|
||||
|
||||
public add (pair: KeyringPair): KeyringPair {
|
||||
this.#map[decodeAddress(pair.address).toString()] = pair;
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
public all (): KeyringPair[] {
|
||||
return Object.values(this.#map);
|
||||
}
|
||||
|
||||
public get (address: string | Uint8Array): KeyringPair {
|
||||
const pair = this.#map[decodeAddress(address).toString()];
|
||||
|
||||
if (!pair) {
|
||||
throw new Error(`Unable to retrieve keypair '${
|
||||
isU8a(address) || isHex(address)
|
||||
? u8aToHex(u8aToU8a(address))
|
||||
: address
|
||||
}'`);
|
||||
}
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
public remove (address: string | Uint8Array): void {
|
||||
delete this.#map[decodeAddress(address).toString()];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
// From https://github.com/paritytech/substrate/wiki/Secret-URI-Test-Vectors
|
||||
|
||||
import type { KeypairType } from '@pezkuwi/util-crypto/types';
|
||||
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
import { cryptoWaitReady } from '@pezkuwi/util-crypto';
|
||||
|
||||
import Keyring from './index.js';
|
||||
|
||||
const PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
|
||||
const ETHEREUM_PHRASE = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
||||
|
||||
const TESTS = {
|
||||
ecdsa: [
|
||||
{
|
||||
pk: '0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1',
|
||||
ss: '5C7C2Z5sWbytvHpuLTvzKunnnRwQxft1jiqrLD5rhucQ5S9X',
|
||||
uri: `${PHRASE}//Alice`
|
||||
}
|
||||
],
|
||||
ethereum: [
|
||||
{
|
||||
pk: '0x0381351b1b46d2602b0992bb5d5531f9c1696b0812feb2534b6884adc47e2e1d8b',
|
||||
ss: '0x31ea8795EE32D782C8ff41a5C68Dcbf0F5B27f6d',
|
||||
uri: `${ETHEREUM_PHRASE}/m/44'/60'/0'/0/0`
|
||||
},
|
||||
{
|
||||
pk: '0x02509540919faacf9ab52146c9aa40db68172d83777250b28e4679176e49ccdd9f',
|
||||
ss: '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac',
|
||||
uri: `${PHRASE}/m/44'/60'/0'/0/0`
|
||||
},
|
||||
{
|
||||
pk: '0x033bc19e36ff1673910575b6727a974a9abd80c9a875d41ab3e2648dbfb9e4b518',
|
||||
ss: '0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0',
|
||||
uri: `${PHRASE}/m/44'/60'/0'/0/1`
|
||||
}
|
||||
],
|
||||
sr25519: [
|
||||
{
|
||||
pk: '0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a',
|
||||
ss: '5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV',
|
||||
uri: PHRASE
|
||||
},
|
||||
{
|
||||
pk: '0xb69355deefa7a8f33e9297f5af22e680f03597a99d4f4b1c44be47e7a2275802',
|
||||
ss: '5GC6LfpV352HtJPySfAecb5JdePtf4R9Vq49NUU8RhzgBqgq',
|
||||
uri: `${PHRASE}///password`
|
||||
},
|
||||
{
|
||||
pk: '0x40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a',
|
||||
ss: '5DXZzrDxHbkQov4QBAY4TjpwnHCMrKXkomTnKSw8UArBEY5v',
|
||||
uri: `${PHRASE}/foo`
|
||||
},
|
||||
{
|
||||
pk: '0x547d4a55642ec7ebadc0bd29b6e570b8c926059b3c0655d4948075e9a7e6f31e',
|
||||
ss: '5DyV6fZuvPemWrUqBgWwTSgoV86w6xms3KhkFU6cQcWxU8eP',
|
||||
uri: `${PHRASE}//foo`
|
||||
},
|
||||
{
|
||||
pk: '0x3841947ffcde6f5fef26fb68b59bb8665637e30e32ec2051f99cf6b9c674fe09',
|
||||
ss: '5DLU27is5iViNopQb2KxsTyPx6j4vCu8X3sk3j3NNLkPCqKM',
|
||||
uri: `${PHRASE}//foo/bar`
|
||||
},
|
||||
{
|
||||
pk: '0xdc142f7476a7b0aa262aeccf207f1d18daa90762db393006741e8a31f39dbc53',
|
||||
ss: '5H3GPTqDSpjkfDwbHy12PD6BWm8jvGSX4xYC8UMprHpTPcRg',
|
||||
uri: `${PHRASE}/foo//bar`
|
||||
},
|
||||
{
|
||||
pk: '0xa2e56b06407a6d1e819d2fc33fa0ec604b29c2e868b70b3696bb049b8725934b',
|
||||
ss: '5FkHmNgbg64MwStgCyDi2Uw3ufFu11mqQgmWT9uwK4Lghvpv',
|
||||
uri: `${PHRASE}//foo/bar//42/69`
|
||||
},
|
||||
{
|
||||
pk: '0x0e0d24e3e1ff2c07f269c99e2e0df8681fda1851ac42fc846ca2daaa90cd8f14',
|
||||
ss: '5CP8S23JBNXYNpJsL7ESPJBNnUZE6itcfM4EnDxEhaVEU6dT',
|
||||
uri: `${PHRASE}//foo/bar//42/69///password`
|
||||
},
|
||||
{
|
||||
pk: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d',
|
||||
ss: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
||||
uri: `${PHRASE}//Alice`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('keyring.addFromUri', (): void => {
|
||||
for (const [type, tests] of Object.entries(TESTS)) {
|
||||
const keyring = new Keyring({ type: type as KeypairType });
|
||||
|
||||
describe(`${type}`, (): void => {
|
||||
tests.forEach(({ pk, ss, uri }): void => {
|
||||
it(`creates ${uri}`, (): void => {
|
||||
const pair = keyring.addFromUri(uri, {}, type as KeypairType);
|
||||
|
||||
expect(u8aToHex(pair.publicKey)).toEqual(pk);
|
||||
expect(pair.address).toEqual(ss);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { KeypairType } from '@pezkuwi/util-crypto/types';
|
||||
import type { KeyringInstance, KeyringOptions } from './types.js';
|
||||
|
||||
import { hexToU8a } from '@pezkuwi/util';
|
||||
|
||||
import { createPair } from './pair/index.js';
|
||||
import { Keyring } from './keyring.js';
|
||||
|
||||
interface PairDef {
|
||||
name?: string;
|
||||
p: HexString;
|
||||
s: HexString;
|
||||
seed?: string;
|
||||
type: KeypairType
|
||||
}
|
||||
|
||||
// NOTE This is not great since we have the secretKey here explicitly, but a testing
|
||||
// keyring is for testing - what happens is that in most cases the keyring is initialises
|
||||
// before anything else. Since the sr25519 crypto is async, this creates problems with
|
||||
// adding the keys when only the keyring is used.
|
||||
export const PAIRSSR25519: PairDef[] = [
|
||||
{
|
||||
p: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d',
|
||||
s: '0x98319d4ff8a9508c4bb0cf0b5a78d760a0b2082c02775e6e82370816fedfff48925a225d97aa00682d6a59b95b18780c10d7032336e88f3442b42361f4a66011', // nosemgrep
|
||||
seed: 'Alice',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f',
|
||||
s: '0xe8da6c9d810e020f5e3c7f5af2dea314cbeaa0d72bc6421e92c0808a0c584a6046ab28e97c3ffc77fe12b5a4d37e8cd4afbfebbf2391ffc7cb07c0f38c023efd', // nosemgrep
|
||||
seed: 'Alice//stash',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48',
|
||||
s: '0x081ff694633e255136bdb456c20a5fc8fed21f8b964c11bb17ff534ce80ebd5941ae88f85d0c1bfc37be41c904e1dfc01de8c8067b0d6d5df25dd1ac0894a325', // nosemgrep
|
||||
seed: 'Bob',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e',
|
||||
s: '0xc006507cdfc267a21532394c49ca9b754ca71de21e15a1cdf807c7ceab6d0b6c3ed408d9d35311540dcd54931933e67cf1ea10d46f75408f82b789d9bd212fde', // nosemgrep
|
||||
seed: 'Bob//stash',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22',
|
||||
s: '0xa8f2d83016052e5d6d77b2f6fd5d59418922a09024cda701b3c34369ec43a7668faf12ff39cd4e5d92bb773972f41a7a5279ebc2ed92264bed8f47d344f8f18c', // nosemgrep
|
||||
seed: 'Charlie',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20',
|
||||
s: '0x20e05482ca4677e0edbc58ae9a3a59f6ed3b1a9484ba17e64d6fe8688b2b7b5d108c4487b9323b98b11fe36cb301b084e920f7b7895536809a6d62a451b25568', // nosemgrep
|
||||
seed: 'Dave',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e',
|
||||
s: '0x683576abfd5dc35273e4264c23095a1bf21c14517bece57c7f0cc5c0ed4ce06a3dbf386b7828f348abe15d76973a72009e6ef86a5c91db2990cb36bb657c6587', // nosemgrep
|
||||
seed: 'Eve',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c',
|
||||
s: '0xb835c20f450079cf4f513900ae9faf8df06ad86c681884122c752a4b2bf74d4303e4f21bc6cc62bb4eeed5a9cce642c25e2d2ac1464093b50f6196d78e3a7426', // nosemgrep
|
||||
seed: 'Ferdie',
|
||||
type: 'sr25519'
|
||||
}
|
||||
];
|
||||
|
||||
export const PAIRSETHEREUM: PairDef[] = [
|
||||
{
|
||||
name: 'Alith',
|
||||
p: '0x02509540919faacf9ab52146c9aa40db68172d83777250b28e4679176e49ccdd9f',
|
||||
s: '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Baltathar',
|
||||
p: '0x033bc19e36ff1673910575b6727a974a9abd80c9a875d41ab3e2648dbfb9e4b518',
|
||||
s: '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Charleth',
|
||||
p: '0x0234637bdc0e89b5d46543bcbf8edff329d2702bc995e27e9af4b1ba009a3c2a5e',
|
||||
s: '0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Dorothy',
|
||||
p: '0x02a00d60b2b408c2a14c5d70cdd2c205db8985ef737a7e55ad20ea32cc9e7c417c',
|
||||
s: '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Ethan',
|
||||
p: '0x025cdc005b752651cd3f728fb9192182acb3a9c89e19072cbd5b03f3ee1f1b3ffa',
|
||||
s: '0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Faith',
|
||||
p: '0x037964b6c9d546da4646ada28a99e34acaa1d14e7aba861a9055f9bd200c8abf74',
|
||||
s: '0xb9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df', // nosemgrep
|
||||
type: 'ethereum'
|
||||
}
|
||||
];
|
||||
|
||||
function createMeta (name?: string, seed?: string) {
|
||||
if (!name && !seed) {
|
||||
throw new Error('Testing pair should have either a name or a seed');
|
||||
}
|
||||
|
||||
return {
|
||||
isTesting: true,
|
||||
name: name || seed?.replace('//', '_').toLowerCase()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @name testKeyring
|
||||
* @summary Create an instance of Keyring pre-populated with locked test accounts
|
||||
* @description The test accounts (i.e. alice, bob, dave, eve, ferdie)
|
||||
* are available on the dev chain and each test account is initialized with DOT funds.
|
||||
*/
|
||||
export function createTestKeyring (options: KeyringOptions = {}, isDerived = true): KeyringInstance {
|
||||
const keyring = new Keyring(options);
|
||||
const pairs = options.type === 'ethereum'
|
||||
? PAIRSETHEREUM
|
||||
: PAIRSSR25519;
|
||||
|
||||
for (const { name, p, s, seed, type } of pairs) {
|
||||
const meta = createMeta(name, seed);
|
||||
const pair = !isDerived && !name && seed
|
||||
? keyring.addFromUri(seed, meta, options.type)
|
||||
: keyring.addPair(
|
||||
createPair(
|
||||
{ toSS58: keyring.encodeAddress, type },
|
||||
{ publicKey: hexToU8a(p), secretKey: hexToU8a(s) },
|
||||
meta
|
||||
)
|
||||
);
|
||||
|
||||
pair.lock = (): void => {
|
||||
// we don't have lock/unlock functionality here
|
||||
};
|
||||
}
|
||||
|
||||
return keyring;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
import { cryptoWaitReady } from '@pezkuwi/util-crypto';
|
||||
|
||||
import Keyring from './index.js';
|
||||
import { createTestPairs } from './testingPairs.js';
|
||||
|
||||
const TEST_ADD = '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('testingPairs', (): void => {
|
||||
it('creates without failing', (): void => {
|
||||
expect(
|
||||
Object.keys(createTestPairs())
|
||||
).toHaveLength(2 + 0 + 7); // stash, session, pairs
|
||||
});
|
||||
|
||||
it('has the correct address for Alice (non-HDKD)', (): void => {
|
||||
expect(
|
||||
createTestPairs({ type: 'ed25519' }, false).alice.address
|
||||
).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');
|
||||
});
|
||||
|
||||
it('has the correct address for Alice (HDKD)', (): void => {
|
||||
expect(
|
||||
createTestPairs({ type: 'ed25519' }).alice.address
|
||||
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
});
|
||||
|
||||
it('has the correct address for Alith (Eth)', (): void => {
|
||||
expect(
|
||||
createTestPairs({ type: 'ethereum' }).Alith.address
|
||||
).toEqual(TEST_ADD);
|
||||
});
|
||||
|
||||
it('has the correct address for Alith (Eth), same as obtained by createFromUri', (): void => {
|
||||
const keyring = new Keyring({ type: 'ethereum' });
|
||||
const pair = keyring.createFromUri('0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133');
|
||||
|
||||
expect(pair?.address).toEqual(TEST_ADD);
|
||||
});
|
||||
|
||||
describe('checks eth test addresses', (): void => {
|
||||
const ring = createTestPairs({ type: 'ethereum' });
|
||||
const keyring = new Keyring({ type: 'ethereum' });
|
||||
// priv keys generated by ganache-cli --mnemonic "bottom drive obey lake curtain smoke basket hold race lonely fit walk"
|
||||
const privKeys: string[] = ['0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133',
|
||||
'0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b',
|
||||
'0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b',
|
||||
'0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68',
|
||||
'0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4',
|
||||
'0xb9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df',
|
||||
'0x96b8a38e12e1a31dee1eab2fffdf9d9990045f5b37e44d8cc27766ef294acf18',
|
||||
'0x0d6dcaaef49272a5411896be8ad16c01c35d6f8c18873387b71fbc734759b0ab',
|
||||
'0x4c42532034540267bf568198ccec4cb822a025da542861fcb146a5fab6433ff8',
|
||||
'0x94c49300a58d576011096bcb006aa06f5a91b34b4383891e8029c21dc39fbb8b'];
|
||||
|
||||
// @ts-expect-error We should not delete from the maps, however this is a test
|
||||
delete ring.nobody;
|
||||
|
||||
Object
|
||||
.keys(ring)
|
||||
.filter((_, i) => i < 6)
|
||||
.forEach((testKeyring, i) => {
|
||||
it(`checks #${i}`, (): void => {
|
||||
expect(
|
||||
u8aToHex(ring[testKeyring].publicKey)
|
||||
).toEqual(
|
||||
u8aToHex(keyring.createFromUri(privKeys[i]).publicKey)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeypairType } from '@pezkuwi/util-crypto/types';
|
||||
import type { KeyringOptions, KeyringPair } from './types.js';
|
||||
|
||||
import { nobody } from './pair/nobody.js';
|
||||
import { createTestKeyring } from './testing.js';
|
||||
|
||||
export interface TestKeyringMap {
|
||||
nobody: KeyringPair;
|
||||
|
||||
[index: string]: KeyringPair;
|
||||
}
|
||||
|
||||
export interface TestKeyringMapSubstrate extends TestKeyringMap {
|
||||
alice: KeyringPair;
|
||||
bob: KeyringPair;
|
||||
charlie: KeyringPair;
|
||||
dave: KeyringPair;
|
||||
eve: KeyringPair;
|
||||
ferdie: KeyringPair;
|
||||
}
|
||||
|
||||
export interface TestKeyringMapEthereum extends TestKeyringMap {
|
||||
Alith: KeyringPair;
|
||||
Baltathar: KeyringPair;
|
||||
Charleth: KeyringPair;
|
||||
Dorothy: KeyringPair;
|
||||
Ethan: KeyringPair;
|
||||
Faith: KeyringPair;
|
||||
}
|
||||
|
||||
export type DetectMap<O extends KeyringOptions | undefined> = DetectPairType<O> extends 'ethereum'
|
||||
? TestKeyringMapEthereum
|
||||
: TestKeyringMapSubstrate;
|
||||
|
||||
export type DetectPairType<O extends KeyringOptions | undefined> = O extends KeyringOptions
|
||||
? O['type'] extends KeypairType
|
||||
? O['type']
|
||||
: 'sr25519'
|
||||
: 'sr25519';
|
||||
|
||||
export function createTestPairs <O extends KeyringOptions, M = DetectMap<O>> (options?: O, isDerived = true): M {
|
||||
const keyring = createTestKeyring(options, isDerived);
|
||||
const pairs = keyring.getPairs();
|
||||
const map: TestKeyringMap = { nobody: nobody() };
|
||||
|
||||
for (const p of pairs) {
|
||||
if (p.meta.name) {
|
||||
map[p.meta.name] = p;
|
||||
}
|
||||
}
|
||||
|
||||
return map as M;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { EncryptedJson, Keypair, KeypairType, Prefix } from '@pezkuwi/util-crypto/types';
|
||||
|
||||
export interface KeyringOptions {
|
||||
/** The ss58Format to use for address encoding (defaults to 42) */
|
||||
ss58Format?: Prefix;
|
||||
/** The type of keyring to create (defaults to ed25519) */
|
||||
type?: KeypairType;
|
||||
}
|
||||
|
||||
export interface KeyringPair$MetaHardware {
|
||||
accountIndex?: number;
|
||||
accountOffset?: number;
|
||||
addressOffset?: number;
|
||||
hardwareType?: 'ledger';
|
||||
}
|
||||
|
||||
export interface KeyringPair$MetaFlags {
|
||||
isDefaultAuthSelected?: boolean;
|
||||
isExternal?: boolean;
|
||||
isHardware?: boolean;
|
||||
isHidden?: boolean;
|
||||
isInjected?: boolean;
|
||||
isMultisig?: boolean;
|
||||
isProxied?: boolean;
|
||||
isRecent?: boolean;
|
||||
isTesting?: boolean;
|
||||
}
|
||||
|
||||
export interface KeyringPair$MetaContract {
|
||||
abi: string;
|
||||
genesisHash?: HexString | null;
|
||||
}
|
||||
|
||||
export interface KeyringPair$MetaExtension {
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface KeyringPair$MetaMultisig {
|
||||
threshold?: number;
|
||||
who?: string[];
|
||||
}
|
||||
|
||||
export interface KeyringPair$MetaParent {
|
||||
parentAddress?: string;
|
||||
parentName?: string;
|
||||
}
|
||||
|
||||
export interface KeyringPair$Meta extends KeyringPair$MetaExtension, KeyringPair$MetaFlags, KeyringPair$MetaHardware, KeyringPair$MetaMultisig, KeyringPair$MetaParent {
|
||||
address?: string;
|
||||
contract?: KeyringPair$MetaContract;
|
||||
genesisHash?: HexString | null;
|
||||
name?: string;
|
||||
suri?: string;
|
||||
tags?: string[];
|
||||
type?: KeypairType;
|
||||
whenCreated?: number;
|
||||
whenEdited?: number;
|
||||
whenUsed?: number;
|
||||
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface KeyringPair$Json extends EncryptedJson {
|
||||
/** The ss58 encoded address or the hex-encoded version (the latter is for ETH-compat chains) */
|
||||
address: string;
|
||||
/** The underlying metadata associated with the keypair */
|
||||
meta: KeyringPair$Meta;
|
||||
}
|
||||
|
||||
export interface SignOptions {
|
||||
/** Create a MultiSignature-compatible output with an indicator type */
|
||||
withType?: boolean;
|
||||
}
|
||||
|
||||
export interface KeyringPair {
|
||||
readonly address: string;
|
||||
readonly addressRaw: Uint8Array;
|
||||
readonly meta: KeyringPair$Meta;
|
||||
readonly isLocked: boolean;
|
||||
readonly publicKey: Uint8Array;
|
||||
readonly type: KeypairType;
|
||||
|
||||
decodePkcs8 (passphrase?: string, encoded?: Uint8Array): void;
|
||||
derive (suri: string, meta?: KeyringPair$Meta): KeyringPair;
|
||||
encodePkcs8 (passphrase?: string): Uint8Array;
|
||||
lock (): void;
|
||||
setMeta (meta: KeyringPair$Meta): void;
|
||||
sign (message: string | Uint8Array, options?: SignOptions): Uint8Array;
|
||||
toJson (passphrase?: string): KeyringPair$Json;
|
||||
unlock (passphrase?: string): void;
|
||||
verify (message: string | Uint8Array, signature: Uint8Array, signerPublic: string | Uint8Array): boolean;
|
||||
vrfSign (message: string | Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): Uint8Array;
|
||||
vrfVerify (message: string | Uint8Array, vrfResult: Uint8Array, signerPublic: string | Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): boolean;
|
||||
}
|
||||
|
||||
export interface KeyringPairs {
|
||||
add: (pair: KeyringPair) => KeyringPair;
|
||||
all: () => KeyringPair[];
|
||||
get: (address: string | Uint8Array) => KeyringPair;
|
||||
remove: (address: string | Uint8Array) => void;
|
||||
}
|
||||
|
||||
export interface KeyringInstance {
|
||||
readonly pairs: KeyringPair[];
|
||||
readonly publicKeys: Uint8Array[];
|
||||
readonly type: KeypairType;
|
||||
|
||||
decodeAddress (encoded: string | Uint8Array, ignoreChecksum?: boolean, ss58Format?: Prefix): Uint8Array;
|
||||
encodeAddress (key: Uint8Array | string, ss58Format?: Prefix): string;
|
||||
setSS58Format (ss58Format: Prefix): void;
|
||||
|
||||
addPair (pair: KeyringPair): KeyringPair;
|
||||
addFromAddress (address: string | Uint8Array, meta?: KeyringPair$Meta, encoded?: Uint8Array | null, type?: KeypairType, ignoreChecksum?: boolean): KeyringPair;
|
||||
addFromJson (pair: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair;
|
||||
addFromMnemonic (mnemonic: string, meta?: KeyringPair$Meta, type?: KeypairType, wordlist?: string[]): KeyringPair;
|
||||
addFromPair (pair: Keypair, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair
|
||||
addFromSeed (seed: Uint8Array, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
|
||||
addFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType, wordlist?: string[]): KeyringPair;
|
||||
createFromJson (json: KeyringPair$Json, ignoreChecksum?: boolean): KeyringPair;
|
||||
createFromPair (pair: Keypair, meta: KeyringPair$Meta, type: KeypairType): KeyringPair
|
||||
createFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType, wordlist?: string[]): KeyringPair;
|
||||
getPair (address: string | Uint8Array): KeyringPair;
|
||||
getPairs (): KeyringPair[];
|
||||
getPublicKeys (): Uint8Array[];
|
||||
removePair (address: string | Uint8Array): void;
|
||||
toJson (address: string | Uint8Array, passphrase?: string): KeyringPair$Json;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/mod.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../util/tsconfig.build.json" },
|
||||
{ "path": "../util-crypto/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"emitDeclarationOnly": false,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"**/*.spec.ts"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../keyring/tsconfig.build.json" },
|
||||
{ "path": "../util/tsconfig.build.json" },
|
||||
{ "path": "../util-crypto/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# @pezkuwi/networks
|
||||
|
||||
A list of all available Substrate networks and their applicable prefixes.
|
||||
|
||||
This list is periodically checked against the master list available at https://github.com/paritytech/ss58-registry/blob/main/ss58-registry.json
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
|
||||
"description": "A list of all available Substrate networks and their applicable prefixes",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/networks#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@pezkuwi/networks",
|
||||
"repository": {
|
||||
"directory": "packages/networks",
|
||||
"type": "git",
|
||||
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "14.0.1",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@pezkuwi/util": "14.0.1",
|
||||
"@substrate/ss58-registry": "^1.51.0",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pezkuwi/hw-ledger": "14.0.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { ledgerApps } from '@pezkuwi/hw-ledger/defaults';
|
||||
import { isHex } from '@pezkuwi/util';
|
||||
|
||||
import { knownGenesis, knownLedger, knownTestnet } from './defaults/index.js';
|
||||
|
||||
describe('defaults', (): void => {
|
||||
const genesisKeys = Object.keys(knownGenesis);
|
||||
const ledgerKeys = Object.keys(knownLedger);
|
||||
|
||||
describe('genesis', (): void => {
|
||||
it('has hex values for all genesis chains', (): void => {
|
||||
expect(
|
||||
genesisKeys.filter((network) =>
|
||||
!knownGenesis[network].length ||
|
||||
knownGenesis[network].some((g) => !isHex(g, 256))
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has no entries for testnets', (): void => {
|
||||
expect(
|
||||
genesisKeys.filter((network) =>
|
||||
knownTestnet[network]
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has genesis for all Ledger chains', (): void => {
|
||||
expect(
|
||||
ledgerKeys.filter((network) =>
|
||||
!knownGenesis[network]
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ledger', (): void => {
|
||||
it('has entries for each of the apps (hwledger -> networks)', (): void => {
|
||||
expect(
|
||||
ledgerKeys.filter((network) =>
|
||||
!ledgerApps[network]
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has entries for each of the apps (networks -> hwledger)', (): void => {
|
||||
expect(
|
||||
Object.keys(ledgerApps).filter((network) =>
|
||||
!knownLedger[network]
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,210 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KnownGenesis } from '../types.js';
|
||||
|
||||
// In the case where the network was hard-spooned and multiple genesisHashes
|
||||
// are provided, it needs to be in reverse order, i.e. most-recent goes first,
|
||||
// oldest goes last. This make lookups for the current a simple genesisHash[0]
|
||||
// where the latest ios always the first entry (See Kusama as an example)
|
||||
//
|
||||
// IMPORTANT: Apart from the test relays, this list is limited to live parachains
|
||||
// and live production networks. It does not and should not contain any testnets,
|
||||
// either stand-alone or connected to test relays such as Westend/Rococo
|
||||
export const knownGenesis: KnownGenesis = {
|
||||
acala: [
|
||||
'0xfc41b9bd8ef8fe53d58c7ea67c794c7ec9a73daf05e6d54b14ff6342c99ba64c'
|
||||
],
|
||||
ajuna: [
|
||||
'0xe358eb1d11b31255a286c12e44fe6780b7edb171d657905a97e39f71d9c6c3ee'
|
||||
],
|
||||
'aleph-node': [
|
||||
'0x70255b4d28de0fc4e1a193d7e175ad1ccef431598211c55538f1018651a0344e'
|
||||
],
|
||||
astar: [
|
||||
'0x9eb76c5184c4ab8679d2d5d819fdf90b9c001403e9e17da2e14b6d8aec4029c6'
|
||||
],
|
||||
basilisk: [
|
||||
'0xa85cfb9b9fd4d622a5b28289a02347af987d8f73fa3108450e2b4a11c1ce5755'
|
||||
],
|
||||
bifrost: [
|
||||
'0x262e1b2ad728475fd6fe88e62d34c200abe6fd693931ddad144059b1eb884e5b'
|
||||
],
|
||||
'bifrost-kusama': [
|
||||
'0x9f28c6a68e0fc9646eff64935684f6eeeece527e37bbe1f213d22caa1d9d6bed'
|
||||
],
|
||||
bittensor: [
|
||||
'0x2f0555cc76fc2840a25a6ea3b9637146806f1f44b090c175ffde2a7e5ab36c03'
|
||||
],
|
||||
centrifuge: [
|
||||
'0xb3db41421702df9a7fcac62b53ffeac85f7853cc4e689e0b93aeb3db18c09d82',
|
||||
'0x67dddf2673b69e5f875f6f25277495834398eafd67f492e09f3f3345e003d1b5'
|
||||
],
|
||||
cere: [
|
||||
'0x81443836a9a24caaa23f1241897d1235717535711d1d3fe24eae4fdc942c092c'
|
||||
],
|
||||
composable: [
|
||||
'0xdaab8df776eb52ec604a5df5d388bb62a050a0aaec4556a64265b9d42755552d'
|
||||
],
|
||||
creditcoin3: [
|
||||
'0x4436a7d64e363df85e065a894721002a86643283f9707338bf195d360ba2ee71', // cc3 mainnet
|
||||
'0xfc4ec97a1c1f119c4353aecb4a17c7c0cf7b40d5d660143d8bad9117e9866572', // cc3 testnet/drynet
|
||||
'0xfc9df99a665f964aed6649f275055e54df5e3420489538ed31d7788f53d11ef6' // cc3 devnet
|
||||
],
|
||||
darwinia: [
|
||||
'0xe71578b37a7c799b0ab4ee87ffa6f059a6b98f71f06fb8c84a8d88013a548ad6'
|
||||
],
|
||||
dentnet: [
|
||||
'0x0313f6a011d128d22f996703cbab05162e2fdc9e031493314fe6db79979c5ca7'
|
||||
],
|
||||
'dock-mainnet': [
|
||||
'0x6bfe24dca2a3be10f22212678ac13a6446ec764103c0f3471c71609eac384aae',
|
||||
'0xf73467c6544aa68df2ee546b135f955c46b90fa627e9b5d7935f41061bb8a5a9'
|
||||
],
|
||||
edgeware: [
|
||||
'0x742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b'
|
||||
],
|
||||
encointer: [
|
||||
'0x7dd99936c1e9e6d1ce7d90eb6f33bea8393b4bf87677d675aa63c9cb3e8c5b5b'
|
||||
],
|
||||
enjin: [
|
||||
'0xd8761d3c88f26dc12875c00d3165f7d67243d56fc85b4cf19937601a7916e5a9'
|
||||
],
|
||||
equilibrium: [
|
||||
'0x6f1a800de3daff7f5e037ddf66ab22ce03ab91874debeddb1086f5f7dbd48925'
|
||||
],
|
||||
frequency: [
|
||||
'0x4a587bf17a404e3572747add7aab7bbe56e805a5479c6c436f07f36fcc8d3ae1'
|
||||
],
|
||||
genshiro: [
|
||||
'0x9b8cefc0eb5c568b527998bdd76c184e2b76ae561be76e4667072230217ea243'
|
||||
],
|
||||
hydradx: [
|
||||
'0xafdc188f45c71dacbaa0b62e16a91f726c7b8699a9748cdf715459de6b7f366d', // Hydration | HydraDX Parachain
|
||||
'0xd2a620c27ec5cbc5621ff9a522689895074f7cca0d08e7134a7804e1a3ba86fc', // Snakenet Gen3-1
|
||||
'0x10af6e84234477d84dc572bac0789813b254aa490767ed06fb9591191d1073f9', // Snakenet Gen3
|
||||
'0x3d75507dd46301767e601265791da1d9cb47b6ebc94e87347b635e5bf58bd047', // Snakenet Gen2
|
||||
'0x0ed32bfcab4a83517fac88f2aa7cbc2f88d3ab93be9a12b6188a036bf8a943c2' // Snakenet Gen1
|
||||
],
|
||||
integritee: [
|
||||
'0xcdedc8eadbfa209d3f207bba541e57c3c58a667b05a2e1d1e86353c9000758da', // on Kusama
|
||||
'0xe13e7af377c64e83f95e0d70d5e5c3c01d697a84538776c5b9bbe0e7d7b6034c' // on Polkadot
|
||||
],
|
||||
'interlay-parachain': [
|
||||
'0xbf88efe70e9e0e916416e8bed61f2b45717f517d7f3523e33c7b001e5ffcbc72'
|
||||
],
|
||||
karura: [
|
||||
'0xbaf5aabe40646d11f0ee8abbdc64f4a4b7674925cba08e4a05ff9ebed6e2126b'
|
||||
],
|
||||
khala: [
|
||||
'0xd43540ba6d3eb4897c28a77d48cb5b729fea37603cbbfc7a86a73b72adb3be8d'
|
||||
],
|
||||
kulupu: [
|
||||
'0xf7a99d3cb92853d00d5275c971c132c074636256583fee53b3bbe60d7b8769ba'
|
||||
],
|
||||
kusama: [
|
||||
'0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe', // Kusama CC3,
|
||||
'0xe3777fa922cafbff200cadeaea1a76bd7898ad5b89f7848999058b50e715f636', // Kusama CC2
|
||||
'0x3fd7b9eb6a00376e5be61f01abb429ffb0b104be05eaff4d458da48fcd425baf' // Kusama CC1
|
||||
],
|
||||
liberland: [
|
||||
'0x6bd89e052d67a45bb60a9a23e8581053d5e0d619f15cb9865946937e690c42d6'
|
||||
],
|
||||
matrixchain: [
|
||||
'0x3af4ff48ec76d2efc8476730f423ac07e25ad48f5f4c9dc39c778b164d808615'
|
||||
],
|
||||
mythos: [
|
||||
'0xf6ee56e9c5277df5b4ce6ae9983ee88f3cbed27d31beeb98f9f84f997a1ab0b9'
|
||||
],
|
||||
nodle: [
|
||||
'0x97da7ede98d7bad4e36b4d734b6055425a3be036da2a332ea5a7037656427a21'
|
||||
],
|
||||
origintrail: [
|
||||
'0xe7e0962324a3b86c83404dbea483f25fb5dab4c224791c81b756cfc948006174'
|
||||
],
|
||||
p3d: [
|
||||
'0x6c5894837ad89b6d92b114a2fb3eafa8fe3d26a54848e3447015442cd6ef4e66'
|
||||
],
|
||||
parallel: [
|
||||
'0xe61a41c53f5dcd0beb09df93b34402aada44cb05117b71059cce40a2723a4e97'
|
||||
],
|
||||
peaq: [
|
||||
'0xd2a5d385932d1f650dae03ef8e2748983779ee342c614f80854d32b8cd8fa48c'
|
||||
],
|
||||
pendulum: [
|
||||
'0x5d3c298622d5634ed019bf61ea4b71655030015bde9beb0d6a24743714462c86'
|
||||
],
|
||||
phala: [
|
||||
'0x1bb969d85965e4bb5a651abbedf21a54b6b31a21f66b5401cc3f1e286268d736'
|
||||
],
|
||||
picasso: [
|
||||
'0x6811a339673c9daa897944dcdac99c6e2939cc88245ed21951a0a3c9a2be75bc',
|
||||
'0xe8e7f0f4c4f5a00720b4821dbfddefea7490bcf0b19009961cc46957984e2c1c'
|
||||
],
|
||||
polimec: [
|
||||
'0x7eb9354488318e7549c722669dcbdcdc526f1fef1420e7944667212f3601fdbd'
|
||||
],
|
||||
polkadex: [
|
||||
'0x3920bcb4960a1eef5580cd5367ff3f430eef052774f78468852f7b9cb39f8a3c'
|
||||
],
|
||||
polkadot: [
|
||||
'0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
|
||||
],
|
||||
polymesh: [
|
||||
'0x6fbd74e5e1d0a61d52ccfe9d4adaed16dd3a7caa37c6bc4d0c2fa12e8b2f4063'
|
||||
],
|
||||
quartz: [
|
||||
'0xcd4d732201ebe5d6b014edda071c4203e16867305332301dc8d092044b28e554'
|
||||
],
|
||||
rococo: [
|
||||
'0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e',
|
||||
'0xaaf2cd1b74b5f726895921259421b534124726263982522174147046b8827897',
|
||||
'0x037f5f3c8e67b314062025fc886fcd6238ea25a4a9b45dce8d246815c9ebe770',
|
||||
'0xc196f81260cf1686172b47a79cf002120735d7cb0eb1474e8adce56618456fff',
|
||||
'0xf6e9983c37baf68846fedafe21e56718790e39fb1c582abc408b81bc7b208f9a',
|
||||
'0x5fce687da39305dfe682b117f0820b319348e8bb37eb16cf34acbf6a202de9d9',
|
||||
'0xe7c3d5edde7db964317cd9b51a3a059d7cd99f81bdbce14990047354334c9779',
|
||||
'0x1611e1dbf0405379b861e2e27daa90f480b2e6d3682414a80835a52e8cb8a215',
|
||||
'0x343442f12fa715489a8714e79a7b264ea88c0d5b8c66b684a7788a516032f6b9',
|
||||
'0x78bcd530c6b3a068bc17473cf5d2aff9c287102bed9af3ae3c41c33b9d6c6147',
|
||||
'0x47381ee0697153d64404fc578392c8fd5cba9073391908f46c888498415647bd',
|
||||
'0x19c0e4fa8ab75f5ac7865e0b8f74ff91eb9a100d336f423cd013a8befba40299'
|
||||
],
|
||||
sora: [
|
||||
'0x7e4e32d0feafd4f9c9414b0be86373f9a1efa904809b683453a9af6856d38ad5'
|
||||
],
|
||||
stafi: [
|
||||
'0x290a4149f09ea0e402c74c1c7e96ae4239588577fe78932f94f5404c68243d80'
|
||||
],
|
||||
statemine: [
|
||||
'0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a'
|
||||
],
|
||||
statemint: [
|
||||
'0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f'
|
||||
],
|
||||
subsocial: [
|
||||
'0x0bd72c1c305172e1275278aaeb3f161e02eccb7a819e63f62d47bd53a28189f8'
|
||||
],
|
||||
ternoa: [
|
||||
'0x6859c81ca95ef624c9dfe4dc6e3381c33e5d6509e35e147092bfbc780f777c4e'
|
||||
],
|
||||
unique: [
|
||||
'0x84322d9cddbf35088f1e54e9a85c967a41a56a4f43445768125e61af166c7d31'
|
||||
],
|
||||
vara: [
|
||||
'0xfe1b4c55fd4d668101126434206571a7838a8b6b93a6d1b95d607e78e6c53763'
|
||||
],
|
||||
vtb: [
|
||||
'0x286bc8414c7000ce1d6ee6a834e29a54c1784814b76243eb77ed0b2c5573c60f',
|
||||
'0x7483b89572fb2bd687c7b9a93b242d0b237f9aba463aba07ec24503931038aaa'
|
||||
],
|
||||
westend: [
|
||||
'0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e'
|
||||
],
|
||||
xxnetwork: [
|
||||
'0x50dd5d206917bf10502c68fb4d18a59fc8aa31586f4e8856b493e43544aa82aa'
|
||||
],
|
||||
zeitgeist: [
|
||||
'0x1bf2a2ecb4a868de66ea8610f2ce7c8c43706561b6476031315f6640fe38e060'
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KnownIcon } from '../types.js';
|
||||
|
||||
// these are icon overrides
|
||||
export const knownIcon: KnownIcon = {
|
||||
centrifuge: 'polkadot',
|
||||
kusama: 'polkadot',
|
||||
polkadot: 'polkadot',
|
||||
sora: 'polkadot',
|
||||
statemine: 'polkadot',
|
||||
statemint: 'polkadot',
|
||||
westmint: 'polkadot'
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { knownGenesis } from './genesis.js';
|
||||
export { knownIcon } from './icons.js';
|
||||
export { knownLedger } from './ledger.js';
|
||||
export { knownTestnet } from './testnets.js';
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KnownLedger } from '../types.js';
|
||||
|
||||
// These match up with the keys of the ledgerApps object in the @polkadot/hw-ledger/defaults.ts
|
||||
// and maps to the known slip44 (minus the `0x8` hard derivation flag)
|
||||
//
|
||||
// NOTE: Any network here needs to have a genesisHash attached in the ./genesis.ts config
|
||||
export const knownLedger: KnownLedger = {
|
||||
acala: 0x00000313,
|
||||
ajuna: 0x00000162,
|
||||
'aleph-node': 0x00000283,
|
||||
astar: 0x0000032a,
|
||||
bifrost: 0x00000314,
|
||||
'bifrost-kusama': 0x00000314,
|
||||
bittensor: 0x00000162,
|
||||
centrifuge: 0x000002eb,
|
||||
composable: 0x00000162,
|
||||
creditcoin3: 0x00000162,
|
||||
darwinia: 0x00000162,
|
||||
dentnet: 0x000002de,
|
||||
'dock-mainnet': 0x00000252,
|
||||
edgeware: 0x0000020b,
|
||||
encointer: 0x000001b2,
|
||||
enjin: 0x00000483,
|
||||
equilibrium: 0x05f5e0fd,
|
||||
frequency: 0x0000082b,
|
||||
genshiro: 0x05f5e0fc,
|
||||
hydradx: 0x00000162,
|
||||
integritee: 0x000007df,
|
||||
'interlay-parachain': 0x00000162,
|
||||
karura: 0x000002ae,
|
||||
khala: 0x000001b2,
|
||||
kusama: 0x000001b2,
|
||||
liberland: 0x000002ff,
|
||||
matrixchain: 0x00000483,
|
||||
mythos: 0x0000003c,
|
||||
nodle: 0x000003eb,
|
||||
origintrail: 0x00000162,
|
||||
parallel: 0x00000162,
|
||||
peaq: 0x00000d0a,
|
||||
pendulum: 0x00000162,
|
||||
phala: 0x00000162,
|
||||
picasso: 0x000001b2,
|
||||
polimec: 0x00000d10,
|
||||
polkadex: 0x0000031f,
|
||||
polkadot: 0x00000162,
|
||||
polymesh: 0x00000253,
|
||||
quartz: 0x00000277,
|
||||
sora: 0x00000269,
|
||||
stafi: 0x0000038b,
|
||||
statemine: 0x000001b2, // common-good on Kusama, shares derivation
|
||||
statemint: 0x00000162, // common-good on Polkadot, shares derivation
|
||||
ternoa: 0x00003e3,
|
||||
unique: 0x00000295,
|
||||
vara: 0x00001370,
|
||||
vtb: 0x000002b6,
|
||||
xxnetwork: 0x000007a3,
|
||||
zeitgeist: 0x00000162
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KnownTestnet } from '../types.js';
|
||||
|
||||
// testnets should not allow selection
|
||||
export const knownTestnet: KnownTestnet = {
|
||||
'': true, // this is the default non-network entry
|
||||
'cess-testnet': true,
|
||||
'dock-testnet': true,
|
||||
jupiter: true,
|
||||
'mathchain-testnet': true,
|
||||
p3dt: true,
|
||||
subspace_testnet: true,
|
||||
'zero-alphaville': true
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import type { SubstrateNetwork } from './types.js';
|
||||
|
||||
import { knownGenesis, knownIcon, knownLedger, knownTestnet } from './defaults/index.js';
|
||||
import { allNetworks, availableNetworks, selectableNetworks } from './index.js';
|
||||
|
||||
describe('availableNetworks', (): void => {
|
||||
it('has the correct starting order', (): void => {
|
||||
expect(availableNetworks.slice(0, 3).map(({ prefix }) => prefix)).toEqual([0, 2, 42]);
|
||||
});
|
||||
|
||||
it('has a sorted list (first external, last external)', (): void => {
|
||||
expect(availableNetworks[3].displayName).toEqual('3DP network');
|
||||
expect(availableNetworks[availableNetworks.length - 1].displayName).toEqual('ZERO');
|
||||
});
|
||||
|
||||
it('has no ignored networks', (): void => {
|
||||
expect(availableNetworks.some(({ isIgnored }) => isIgnored)).toEqual(false);
|
||||
});
|
||||
|
||||
it('has no reserved networks', (): void => {
|
||||
expect(availableNetworks.some(({ prefix }) => prefix === 47)).toEqual(false);
|
||||
});
|
||||
|
||||
it('has allNetworks genesis information', (): void => {
|
||||
expect(
|
||||
Object.entries(knownGenesis).filter(([network, genesisHash]) =>
|
||||
availableNetworks.some((a) =>
|
||||
a.network === network &&
|
||||
genesisHash.some((g, index) => a.genesisHash[index] !== g)
|
||||
)
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has allNetworks ledger details', (): void => {
|
||||
expect(
|
||||
Object.entries(knownLedger).filter(([network, slip44]) =>
|
||||
availableNetworks.some((a) =>
|
||||
a.network === network && (
|
||||
a.slip44 !== slip44 ||
|
||||
!a.hasLedgerSupport ||
|
||||
!a.genesisHash.length
|
||||
)
|
||||
)
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has no testnets exposed', (): void => {
|
||||
expect(
|
||||
Object.keys(knownTestnet).filter((network) =>
|
||||
availableNetworks.some((a) =>
|
||||
a.network === network
|
||||
)
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has allNetworks icons, except for overrides', (): void => {
|
||||
expect(
|
||||
availableNetworks.filter(({ icon, network }) =>
|
||||
icon !== 'substrate' &&
|
||||
knownIcon[network] !== icon
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('has all the correct fields', (): void => {
|
||||
expect(availableNetworks[0]).toEqual({
|
||||
decimals: [10],
|
||||
displayName: 'Polkadot Relay Chain',
|
||||
genesisHash: [
|
||||
'0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
|
||||
],
|
||||
hasLedgerSupport: true,
|
||||
icon: 'polkadot',
|
||||
isIgnored: false,
|
||||
isTestnet: false,
|
||||
network: 'polkadot',
|
||||
prefix: 0,
|
||||
slip44: 354,
|
||||
standardAccount: '*25519',
|
||||
symbols: ['DOT'],
|
||||
website: 'https://polkadot.network'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('allNetworks', (): void => {
|
||||
it('has no ss58 duplicates', (): void => {
|
||||
const dupes: SubstrateNetwork[] = [];
|
||||
const uniques: SubstrateNetwork[] = [];
|
||||
|
||||
allNetworks.forEach((a): void => {
|
||||
if (uniques.some((u) => u.prefix === a.prefix)) {
|
||||
dupes.push(a);
|
||||
} else {
|
||||
uniques.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
expect(dupes).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectableNetworks', (): void => {
|
||||
it('has the correct starting order', (): void => {
|
||||
expect(selectableNetworks.slice(0, 3).map(({ prefix }) => prefix)).toEqual([0, 2, 42]);
|
||||
});
|
||||
|
||||
it('has a sorted list (first external, last external)', (): void => {
|
||||
expect(selectableNetworks[3].displayName).toEqual('3DP network');
|
||||
expect(selectableNetworks[selectableNetworks.length - 1].displayName).toEqual('Zeitgeist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// TODO: This was removed, really cannot recall the reason...
|
||||
// ... put it back, but keep it removed
|
||||
// import './packageDetect.js';
|
||||
|
||||
export * from './interfaces.js';
|
||||
export { packageInfo } from './packageInfo.js';
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KnownSubstrate, Network, SubstrateNetwork } from './types.js';
|
||||
|
||||
import knownSubstrate from '@substrate/ss58-registry';
|
||||
|
||||
import { knownGenesis, knownIcon, knownLedger, knownTestnet } from './defaults/index.js';
|
||||
|
||||
// These are known prefixes that are not sorted
|
||||
const UNSORTED = [0, 2, 42];
|
||||
const TESTNETS = ['testnet'];
|
||||
|
||||
function toExpanded (o: KnownSubstrate): SubstrateNetwork {
|
||||
const network = o.network || '';
|
||||
const nameParts = network.replace(/_/g, '-').split('-');
|
||||
const n = o as SubstrateNetwork;
|
||||
|
||||
// ledger additions
|
||||
n.slip44 = knownLedger[network];
|
||||
n.hasLedgerSupport = !!n.slip44;
|
||||
|
||||
// general items
|
||||
n.genesisHash = knownGenesis[network] || [];
|
||||
n.icon = knownIcon[network] || 'substrate';
|
||||
|
||||
// filtering
|
||||
n.isTestnet = !!knownTestnet[network] || TESTNETS.includes(nameParts[nameParts.length - 1]);
|
||||
n.isIgnored = n.isTestnet || (
|
||||
!(
|
||||
o.standardAccount &&
|
||||
o.decimals?.length &&
|
||||
o.symbols?.length
|
||||
) &&
|
||||
o.prefix !== 42
|
||||
);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
function filterSelectable ({ genesisHash, prefix }: Network): boolean {
|
||||
return !!genesisHash.length || prefix === 42;
|
||||
}
|
||||
|
||||
function filterAvailable (n: SubstrateNetwork): n is Network {
|
||||
return !n.isIgnored && !!n.network;
|
||||
}
|
||||
|
||||
function sortNetworks (a: Network, b: Network): number {
|
||||
const isUnSortedA = UNSORTED.includes(a.prefix);
|
||||
const isUnSortedB = UNSORTED.includes(b.prefix);
|
||||
|
||||
return isUnSortedA === isUnSortedB
|
||||
? isUnSortedA
|
||||
? 0
|
||||
: a.displayName.localeCompare(b.displayName)
|
||||
: isUnSortedA
|
||||
? -1
|
||||
: 1;
|
||||
}
|
||||
|
||||
// This is all the Substrate networks with our additional information
|
||||
export const allNetworks = knownSubstrate.map(toExpanded);
|
||||
|
||||
// The list of available/claimed prefixes
|
||||
// - no testnets
|
||||
// - we only include those where we have a standardAccount
|
||||
// - sort by name, however we keep 0, 2, 42 first in the list
|
||||
export const availableNetworks = allNetworks.filter(filterAvailable).sort(sortNetworks);
|
||||
|
||||
// A filtered list of those chains we have details about (genesisHashes)
|
||||
export const selectableNetworks = availableNetworks.filter(filterSelectable);
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export * from './index.js';
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
// (packageInfo imports will be kept as-is, user-editable)
|
||||
|
||||
import { detectPackage } from '@pezkuwi/util';
|
||||
|
||||
import { packageInfo } from './packageInfo.js';
|
||||
|
||||
detectPackage(packageInfo, null, []);
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/networks', path: 'auto', type: 'auto', version: '14.0.1' };
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2017-2025 @polkadot/networks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import known from '@substrate/ss58-registry';
|
||||
import fs from 'node:fs';
|
||||
|
||||
import { stringify } from '@pezkuwi/util';
|
||||
|
||||
describe('@substrate/ss58-registry', (): void => {
|
||||
it('has known values', (): void => {
|
||||
const testUrl = new URL('../test/ss58registry.json', import.meta.url);
|
||||
const json = stringify(known, 2);
|
||||
|
||||
try {
|
||||
expect(
|
||||
JSON.parse(json)
|
||||
).toEqual(
|
||||
JSON.parse(
|
||||
fs.readFileSync(testUrl, 'utf-8')
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
if (process.env['GITHUB_REPOSITORY']) {
|
||||
console.error(json);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
fs.writeFileSync(testUrl, json, { flag: 'w' });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2025 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { RegistryEntry } from '@substrate/ss58-registry';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
export type Icon = 'beachball' | 'empty' | 'jdenticon' | 'polkadot' | 'substrate';
|
||||
|
||||
export type KnownIcon = Record<string, Icon>;
|
||||
|
||||
export type KnownLedger = Record<string, number>;
|
||||
|
||||
export type KnownGenesis = Record<string, HexString[]>;
|
||||
|
||||
export type KnownSubstrate = RegistryEntry;
|
||||
|
||||
export type KnownTestnet = Record<string, true>;
|
||||
|
||||
export interface SubstrateNetwork extends KnownSubstrate {
|
||||
/** The genesisHash for the chain */
|
||||
genesisHash: HexString[];
|
||||
/** Does the chain has support for Ledger devices */
|
||||
hasLedgerSupport: boolean;
|
||||
/** The IdentityIcon to use for the chain */
|
||||
icon: Icon;
|
||||
/** Flag set when we don't include this chain */
|
||||
isIgnored: boolean;
|
||||
/** Flag to indicate a testnet */
|
||||
isTestnet: boolean;
|
||||
/** The Ledger-specific/required slip44 for the chain */
|
||||
slip44?: number | null;
|
||||
}
|
||||
|
||||
export interface Network extends SubstrateNetwork {
|
||||
/** The network assigned to this chain */
|
||||
network: string;
|
||||
}
|
||||
|
||||
export interface Ss58Registry {
|
||||
registry: KnownSubstrate[];
|
||||
specification: string;
|
||||
schema: Record<keyof KnownSubstrate, string>;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user