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