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