UNPKG

9.27 kBJavaScriptView Raw
1// Copyright 2017-2022 @polkadot/keyring authors & contributors
2// SPDX-License-Identifier: Apache-2.0
3import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util';
4import { blake2AsU8a, convertPublicKeyToCurve25519, convertSecretKeyToCurve25519, ed25519PairFromSeed as ed25519FromSeed, ed25519Sign, ethereumEncode, keccakAsU8a, keyExtractPath, keyFromPath, naclOpen, naclSeal, secp256k1Compress, secp256k1Expand, secp256k1PairFromSeed as secp256k1FromSeed, secp256k1Sign, signatureVerify, sr25519PairFromSeed as sr25519FromSeed, sr25519Sign, sr25519VrfSign, sr25519VrfVerify } from '@polkadot/util-crypto';
5import { decodePair } from "./decode.js";
6import { encodePair } from "./encode.js";
7import { pairToJson } from "./toJson.js";
8const SIG_TYPE_NONE = new Uint8Array();
9const TYPE_FROM_SEED = {
10 ecdsa: secp256k1FromSeed,
11 ed25519: ed25519FromSeed,
12 ethereum: secp256k1FromSeed,
13 sr25519: sr25519FromSeed
14};
15const TYPE_PREFIX = {
16 ecdsa: new Uint8Array([2]),
17 ed25519: new Uint8Array([0]),
18 ethereum: new Uint8Array([2]),
19 sr25519: new Uint8Array([1])
20};
21const TYPE_SIGNATURE = {
22 ecdsa: (m, p) => secp256k1Sign(m, p, 'blake2'),
23 ed25519: ed25519Sign,
24 ethereum: (m, p) => secp256k1Sign(m, p, 'keccak'),
25 sr25519: sr25519Sign
26};
27const TYPE_ADDRESS = {
28 ecdsa: p => p.length > 32 ? blake2AsU8a(p) : p,
29 ed25519: p => p,
30 ethereum: p => p.length === 20 ? p : keccakAsU8a(secp256k1Expand(p)),
31 sr25519: p => p
32};
33
34function isLocked(secretKey) {
35 return !secretKey || u8aEmpty(secretKey);
36}
37
38function vrfHash(proof, context, extra) {
39 return blake2AsU8a(u8aConcat(context || '', extra || '', proof));
40}
41/**
42 * @name createPair
43 * @summary Creates a keyring pair object
44 * @description Creates a keyring pair object with provided account public key, metadata, and encoded arguments.
45 * The keyring pair stores the account state including the encoded address and associated metadata.
46 *
47 * It has properties whose values are functions that may be called to perform account actions:
48 *
49 * - `address` function retrieves the address associated with the account.
50 * - `decodedPkcs8` function is called with the account passphrase and account encoded public key.
51 * It decodes the encoded public key using the passphrase provided to obtain the decoded account public key
52 * and associated secret key that are then available in memory, and changes the account address stored in the
53 * state of the pair to correspond to the address of the decoded public key.
54 * - `encodePkcs8` function when provided with the correct passphrase associated with the account pair
55 * and when the secret key is in memory (when the account pair is not locked) it returns an encoded
56 * public key of the account.
57 * - `meta` is the metadata that is stored in the state of the pair, either when it was originally
58 * created or set via `setMeta`.
59 * - `publicKey` returns the public key stored in memory for the pair.
60 * - `sign` may be used to return a signature by signing a provided message with the secret
61 * key (if it is in memory) using Nacl.
62 * - `toJson` calls another `toJson` function and provides the state of the pair,
63 * it generates arguments to be passed to the other `toJson` function including an encoded public key of the account
64 * that it generates using the secret key from memory (if it has been made available in memory)
65 * and the optionally provided passphrase argument. It passes a third boolean argument to `toJson`
66 * indicating whether the public key has been encoded or not (if a passphrase argument was provided then it is encoded).
67 * The `toJson` function that it calls returns a JSON object with properties including the `address`
68 * and `meta` that are assigned with the values stored in the corresponding state variables of the account pair,
69 * an `encoded` property that is assigned with the encoded public key in hex format, and an `encoding`
70 * property that indicates whether the public key value of the `encoded` property is encoded or not.
71 */
72
73
74export function createPair({
75 toSS58,
76 type
77}, {
78 publicKey,
79 secretKey
80}, meta = {}, encoded = null, encTypes) {
81 const decodePkcs8 = (passphrase, userEncoded) => {
82 const decoded = decodePair(passphrase, userEncoded || encoded, encTypes);
83
84 if (decoded.secretKey.length === 64) {
85 publicKey = decoded.publicKey;
86 secretKey = decoded.secretKey;
87 } else {
88 const pair = TYPE_FROM_SEED[type](decoded.secretKey);
89 publicKey = pair.publicKey;
90 secretKey = pair.secretKey;
91 }
92 };
93
94 const recode = passphrase => {
95 isLocked(secretKey) && encoded && decodePkcs8(passphrase, encoded);
96 encoded = encodePair({
97 publicKey,
98 secretKey
99 }, passphrase); // re-encode, latest version
100
101 encTypes = undefined; // swap to defaults, latest version follows
102
103 return encoded;
104 };
105
106 const encodeAddress = () => {
107 const raw = TYPE_ADDRESS[type](publicKey);
108 return type === 'ethereum' ? ethereumEncode(raw) : toSS58(raw);
109 };
110
111 return {
112 get address() {
113 return encodeAddress();
114 },
115
116 get addressRaw() {
117 const raw = TYPE_ADDRESS[type](publicKey);
118 return type === 'ethereum' ? raw.slice(-20) : raw;
119 },
120
121 get isLocked() {
122 return isLocked(secretKey);
123 },
124
125 get meta() {
126 return meta;
127 },
128
129 get publicKey() {
130 return publicKey;
131 },
132
133 get type() {
134 return type;
135 },
136
137 // eslint-disable-next-line sort-keys
138 decodePkcs8,
139 decryptMessage: (encryptedMessageWithNonce, senderPublicKey) => {
140 if (isLocked(secretKey)) {
141 throw new Error('Cannot encrypt with a locked key pair');
142 } else if (['ecdsa', 'ethereum'].includes(type)) {
143 throw new Error('Secp256k1 not supported yet');
144 }
145
146 const messageU8a = u8aToU8a(encryptedMessageWithNonce);
147 return naclOpen(messageU8a.slice(24, messageU8a.length), messageU8a.slice(0, 24), convertPublicKeyToCurve25519(u8aToU8a(senderPublicKey)), convertSecretKeyToCurve25519(secretKey));
148 },
149 derive: (suri, meta) => {
150 if (type === 'ethereum') {
151 throw new Error('Unable to derive on this keypair');
152 } else if (isLocked(secretKey)) {
153 throw new Error('Cannot derive on a locked keypair');
154 }
155
156 const {
157 path
158 } = keyExtractPath(suri);
159 const derived = keyFromPath({
160 publicKey,
161 secretKey
162 }, path, type);
163 return createPair({
164 toSS58,
165 type
166 }, derived, meta, null);
167 },
168 encodePkcs8: passphrase => {
169 return recode(passphrase);
170 },
171 encryptMessage: (message, recipientPublicKey, nonceIn) => {
172 if (isLocked(secretKey)) {
173 throw new Error('Cannot encrypt with a locked key pair');
174 } else if (['ecdsa', 'ethereum'].includes(type)) {
175 throw new Error('Secp256k1 not supported yet');
176 }
177
178 const {
179 nonce,
180 sealed
181 } = naclSeal(u8aToU8a(message), convertSecretKeyToCurve25519(secretKey), convertPublicKeyToCurve25519(u8aToU8a(recipientPublicKey)), nonceIn);
182 return u8aConcat(nonce, sealed);
183 },
184 lock: () => {
185 secretKey = new Uint8Array();
186 },
187 setMeta: additional => {
188 meta = objectSpread({}, meta, additional);
189 },
190 sign: (message, options = {}) => {
191 if (isLocked(secretKey)) {
192 throw new Error('Cannot sign with a locked key pair');
193 }
194
195 return u8aConcat(options.withType ? TYPE_PREFIX[type] : SIG_TYPE_NONE, TYPE_SIGNATURE[type](u8aToU8a(message), {
196 publicKey,
197 secretKey
198 }));
199 },
200 toJson: passphrase => {
201 // NOTE: For ecdsa and ethereum, the publicKey cannot be extracted from the address. For these
202 // pass the hex-encoded publicKey through to the address portion of the JSON (before decoding)
203 // unless the publicKey is already an address
204 const address = ['ecdsa', 'ethereum'].includes(type) ? publicKey.length === 20 ? u8aToHex(publicKey) : u8aToHex(secp256k1Compress(publicKey)) : encodeAddress();
205 return pairToJson(type, {
206 address,
207 meta
208 }, recode(passphrase), !!passphrase);
209 },
210 unlock: passphrase => {
211 return decodePkcs8(passphrase);
212 },
213 verify: (message, signature, signerPublic) => {
214 return signatureVerify(message, signature, TYPE_ADDRESS[type](u8aToU8a(signerPublic))).isValid;
215 },
216 vrfSign: (message, context, extra) => {
217 if (isLocked(secretKey)) {
218 throw new Error('Cannot sign with a locked key pair');
219 }
220
221 if (type === 'sr25519') {
222 return sr25519VrfSign(message, {
223 secretKey
224 }, context, extra);
225 }
226
227 const proof = TYPE_SIGNATURE[type](u8aToU8a(message), {
228 publicKey,
229 secretKey
230 });
231 return u8aConcat(vrfHash(proof, context, extra), proof);
232 },
233 vrfVerify: (message, vrfResult, signerPublic, context, extra) => {
234 if (type === 'sr25519') {
235 return sr25519VrfVerify(message, vrfResult, publicKey, context, extra);
236 }
237
238 const result = signatureVerify(message, u8aConcat(TYPE_PREFIX[type], vrfResult.subarray(32)), TYPE_ADDRESS[type](u8aToU8a(signerPublic)));
239 return result.isValid && u8aEq(vrfResult.subarray(0, 32), vrfHash(vrfResult.subarray(32), context, extra));
240 }
241 };
242}
\No newline at end of file