UNPKG

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