1 | import { hexToU8a, isHex, stringToU8a } from '@polkadot/util';
|
2 | import { base64Decode, decodeAddress, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, ethereumEncode, hdEthereum, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed } from '@polkadot/util-crypto';
|
3 | import { createPair } from './pair/index.js';
|
4 | import { DEV_PHRASE } from './defaults.js';
|
5 | import { Pairs } from './pairs.js';
|
6 | const PairFromSeed = {
|
7 | ecdsa: (seed) => secp256k1FromSeed(seed),
|
8 | ed25519: (seed) => ed25519FromSeed(seed),
|
9 | ethereum: (seed) => secp256k1FromSeed(seed),
|
10 | sr25519: (seed) => sr25519FromSeed(seed)
|
11 | };
|
12 | function pairToPublic({ publicKey }) {
|
13 | return publicKey;
|
14 | }
|
15 | /**
|
16 | * # @polkadot/keyring
|
17 | *
|
18 | * ## Overview
|
19 | *
|
20 | * @name Keyring
|
21 | * @summary Keyring management of user accounts
|
22 | * @description Allows generation of keyring pairs from a variety of input combinations, such as
|
23 | * json object containing account address or public key, account metadata, and account encoded using
|
24 | * `addFromJson`, or by providing those values as arguments separately to `addFromAddress`,
|
25 | * or by providing the mnemonic (seed phrase) and account metadata as arguments to `addFromMnemonic`.
|
26 | * Stores the keyring pairs in a keyring pair dictionary. Removal of the keyring pairs from the keyring pair
|
27 | * dictionary is achieved using `removePair`. Retrieval of all the stored pairs via `getPairs` or perform
|
28 | * lookup of a pair for a given account address or public key using `getPair`. JSON metadata associated with
|
29 | * an account may be obtained using `toJson` accompanied by the account passphrase.
|
30 | */
|
31 | export class Keyring {
|
32 | __internal__pairs;
|
33 | __internal__type;
|
34 | __internal__ss58;
|
35 | decodeAddress = decodeAddress;
|
36 | constructor(options = {}) {
|
37 | options.type = options.type || 'ed25519';
|
38 | if (!['ecdsa', 'ethereum', 'ed25519', 'sr25519'].includes(options.type || 'undefined')) {
|
39 | throw new Error(`Expected a keyring type of either 'ed25519', 'sr25519', 'ethereum' or 'ecdsa', found '${options.type || 'unknown'}`);
|
40 | }
|
41 | this.__internal__pairs = new Pairs();
|
42 | this.__internal__ss58 = options.ss58Format;
|
43 | this.__internal__type = options.type;
|
44 | }
|
45 | /**
|
46 | * @description retrieve the pairs (alias for getPairs)
|
47 | */
|
48 | get pairs() {
|
49 | return this.getPairs();
|
50 | }
|
51 | /**
|
52 | * @description retrieve the publicKeys (alias for getPublicKeys)
|
53 | */
|
54 | get publicKeys() {
|
55 | return this.getPublicKeys();
|
56 | }
|
57 | /**
|
58 | * @description Returns the type of the keyring, ed25519, sr25519 or ecdsa
|
59 | */
|
60 | get type() {
|
61 | return this.__internal__type;
|
62 | }
|
63 | /**
|
64 | * @name addPair
|
65 | * @summary Stores an account, given a keyring pair, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
66 | */
|
67 | addPair(pair) {
|
68 | return this.__internal__pairs.add(pair);
|
69 | }
|
70 | /**
|
71 | * @name addFromAddress
|
72 | * @summary Stores an account, given an account address, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
73 | * @description Allows user to explicitly provide separate inputs including account address or public key, and optionally
|
74 | * the associated account metadata, and the default encoded value as arguments (that may be obtained from the json file
|
75 | * of an account backup), and then generates a keyring pair from them that it passes to
|
76 | * `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.
|
77 | */
|
78 | addFromAddress(address, meta = {}, encoded = null, type = this.type, ignoreChecksum, encType) {
|
79 | const publicKey = this.decodeAddress(address, ignoreChecksum);
|
80 | return this.addPair(createPair({ toSS58: this.encodeAddress, type }, { publicKey, secretKey: new Uint8Array() }, meta, encoded, encType));
|
81 | }
|
82 | /**
|
83 | * @name addFromJson
|
84 | * @summary Stores an account, given JSON data, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
85 | * @description Allows user to provide a json object argument that contains account information (that may be obtained from the json file
|
86 | * of an account backup), and then generates a keyring pair from it that it passes to
|
87 | * `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.
|
88 | */
|
89 | addFromJson(json, ignoreChecksum) {
|
90 | return this.addPair(this.createFromJson(json, ignoreChecksum));
|
91 | }
|
92 | /**
|
93 | * @name addFromMnemonic
|
94 | * @summary Stores an account, given a mnemonic, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
95 | * @description Allows user to provide a mnemonic (seed phrase that is provided when account is originally created)
|
96 | * argument and a metadata argument that contains account information (that may be obtained from the json file
|
97 | * of an account backup), and then generates a keyring pair from it that it passes to
|
98 | * `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.
|
99 | */
|
100 | addFromMnemonic(mnemonic, meta = {}, type = this.type) {
|
101 | return this.addFromUri(mnemonic, meta, type);
|
102 | }
|
103 | /**
|
104 | * @name addFromPair
|
105 | * @summary Stores an account created from an explicit publicKey/secreteKey combination
|
106 | */
|
107 | addFromPair(pair, meta = {}, type = this.type) {
|
108 | return this.addPair(this.createFromPair(pair, meta, type));
|
109 | }
|
110 | /**
|
111 | * @name addFromSeed
|
112 | * @summary Stores an account, given seed data, as a Key/Value (public key, pair) in Keyring Pair Dictionary
|
113 | * @description Stores in a keyring pair dictionary the public key of the pair as a key and the pair as the associated value.
|
114 | * Allows user to provide the account seed as an argument, and then generates a keyring pair from it that it passes to
|
115 | * `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.
|
116 | */
|
117 | addFromSeed(seed, meta = {}, type = this.type) {
|
118 | return this.addPair(createPair({ toSS58: this.encodeAddress, type }, PairFromSeed[type](seed), meta, null));
|
119 | }
|
120 | /**
|
121 | * @name addFromUri
|
122 | * @summary Creates an account via an suri
|
123 | * @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)
|
124 | */
|
125 | addFromUri(suri, meta = {}, type = this.type) {
|
126 | return this.addPair(this.createFromUri(suri, meta, type));
|
127 | }
|
128 | /**
|
129 | * @name createFromJson
|
130 | * @description Creates a pair from a JSON keyfile
|
131 | */
|
132 | createFromJson({ address, encoded, encoding: { content, type, version }, meta }, ignoreChecksum) {
|
133 | if (version === '3' && content[0] !== 'pkcs8') {
|
134 | throw new Error(`Unable to decode non-pkcs8 type, [${content.join(',')}] found}`);
|
135 | }
|
136 | const cryptoType = version === '0' || !Array.isArray(content)
|
137 | ? this.type
|
138 | : content[1];
|
139 | const encType = !Array.isArray(type)
|
140 | ? [type]
|
141 | : type;
|
142 | if (!['ed25519', 'sr25519', 'ecdsa', 'ethereum'].includes(cryptoType)) {
|
143 | throw new Error(`Unknown crypto type ${cryptoType}`);
|
144 | }
|
145 | // 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
|
146 | const publicKey = isHex(address)
|
147 | ? hexToU8a(address)
|
148 | : this.decodeAddress(address, ignoreChecksum);
|
149 | const decoded = isHex(encoded)
|
150 | ? hexToU8a(encoded)
|
151 | : base64Decode(encoded);
|
152 | return createPair({ toSS58: this.encodeAddress, type: cryptoType }, { publicKey, secretKey: new Uint8Array() }, meta, decoded, encType);
|
153 | }
|
154 | /**
|
155 | * @name createFromPair
|
156 | * @summary Creates a pair from an explicit publicKey/secreteKey combination
|
157 | */
|
158 | createFromPair(pair, meta = {}, type = this.type) {
|
159 | return createPair({ toSS58: this.encodeAddress, type }, pair, meta, null);
|
160 | }
|
161 | /**
|
162 | * @name createFromUri
|
163 | * @summary Creates a Keypair from an suri
|
164 | * @description This creates a pair from the suri, but does not add it to the keyring
|
165 | */
|
166 | createFromUri(_suri, meta = {}, type = this.type) {
|
167 | // here we only aut-add the dev phrase if we have a hard-derived path
|
168 | const suri = _suri.startsWith('//')
|
169 | ? `${DEV_PHRASE}${_suri}`
|
170 | : _suri;
|
171 | const { derivePath, password, path, phrase } = keyExtractSuri(suri);
|
172 | let seed;
|
173 | const isPhraseHex = isHex(phrase, 256);
|
174 | if (isPhraseHex) {
|
175 | seed = hexToU8a(phrase);
|
176 | }
|
177 | else {
|
178 | const parts = phrase.split(' ');
|
179 | if ([12, 15, 18, 21, 24].includes(parts.length)) {
|
180 | seed = type === 'ethereum'
|
181 | ? mnemonicToLegacySeed(phrase, '', false, 64)
|
182 | : mnemonicToMiniSecret(phrase, password);
|
183 | }
|
184 | else {
|
185 | if (phrase.length > 32) {
|
186 | throw new Error('specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');
|
187 | }
|
188 | seed = stringToU8a(phrase.padEnd(32));
|
189 | }
|
190 | }
|
191 | const derived = type === 'ethereum'
|
192 | ? isPhraseHex
|
193 | ? PairFromSeed[type](seed) // for eth, if the private key is provided as suri, it must be derived only once
|
194 | : hdEthereum(seed, derivePath.substring(1))
|
195 | : keyFromPath(PairFromSeed[type](seed), path, type);
|
196 | return createPair({ toSS58: this.encodeAddress, type }, derived, meta, null);
|
197 | }
|
198 | /**
|
199 | * @name encodeAddress
|
200 | * @description Encodes the input into an ss58 representation
|
201 | */
|
202 | encodeAddress = (address, ss58Format) => {
|
203 | return this.type === 'ethereum'
|
204 | ? ethereumEncode(address)
|
205 | : encodeAddress(address, ss58Format ?? this.__internal__ss58);
|
206 | };
|
207 | /**
|
208 | * @name getPair
|
209 | * @summary Retrieves an account keyring pair from the Keyring Pair Dictionary, given an account address
|
210 | * @description Returns a keyring pair value from the keyring pair dictionary by performing
|
211 | * a key lookup using the provided account address or public key (after decoding it).
|
212 | */
|
213 | getPair(address) {
|
214 | return this.__internal__pairs.get(address);
|
215 | }
|
216 | /**
|
217 | * @name getPairs
|
218 | * @summary Retrieves all account keyring pairs from the Keyring Pair Dictionary
|
219 | * @description Returns an array list of all the keyring pair values that are stored in the keyring pair dictionary.
|
220 | */
|
221 | getPairs() {
|
222 | return this.__internal__pairs.all();
|
223 | }
|
224 | /**
|
225 | * @name getPublicKeys
|
226 | * @summary Retrieves Public Keys of all Keyring Pairs stored in the Keyring Pair Dictionary
|
227 | * @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.
|
228 | */
|
229 | getPublicKeys() {
|
230 | return this.__internal__pairs.all().map(pairToPublic);
|
231 | }
|
232 | /**
|
233 | * @name removePair
|
234 | * @description Deletes the provided input address or public key from the stored Keyring Pair Dictionary.
|
235 | */
|
236 | removePair(address) {
|
237 | this.__internal__pairs.remove(address);
|
238 | }
|
239 | /**
|
240 | * @name setSS58Format;
|
241 | * @description Sets the ss58 format for the keyring
|
242 | */
|
243 | setSS58Format(ss58) {
|
244 | this.__internal__ss58 = ss58;
|
245 | }
|
246 | /**
|
247 | * @name toJson
|
248 | * @summary Returns a JSON object associated with the input argument that contains metadata assocated with an account
|
249 | * @description Returns a JSON object containing the metadata associated with an account
|
250 | * when valid address or public key and when the account passphrase is provided if the account secret
|
251 | * is not already unlocked and available in memory. Note that in [Polkadot-JS Apps](https://github.com/polkadot-js/apps) the user
|
252 | * may backup their account to a JSON file that contains this information.
|
253 | */
|
254 | toJson(address, passphrase) {
|
255 | return this.__internal__pairs.get(address).toJson(passphrase);
|
256 | }
|
257 | }
|