1 | import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util';
|
2 | import { 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';
|
3 | import { decodePair } from './decode.js';
|
4 | import { encodePair } from './encode.js';
|
5 | import { pairToJson } from './toJson.js';
|
6 | const SIG_TYPE_NONE = new Uint8Array();
|
7 | const TYPE_FROM_SEED = {
|
8 | ecdsa: secp256k1FromSeed,
|
9 | ed25519: ed25519FromSeed,
|
10 | ethereum: secp256k1FromSeed,
|
11 | sr25519: sr25519FromSeed
|
12 | };
|
13 | const TYPE_PREFIX = {
|
14 | ecdsa: new Uint8Array([2]),
|
15 | ed25519: new Uint8Array([0]),
|
16 | ethereum: new Uint8Array([2]),
|
17 | sr25519: new Uint8Array([1])
|
18 | };
|
19 | const 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 | };
|
25 | const 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 | };
|
31 | function isLocked(secretKey) {
|
32 | return !secretKey || u8aEmpty(secretKey);
|
33 | }
|
34 | function vrfHash(proof, context, extra) {
|
35 | return blake2AsU8a(u8aConcat(context || '', extra || '', proof));
|
36 | }
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 | export 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);
|
84 | encTypes = undefined;
|
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 |
|
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 |
|
147 |
|
148 |
|
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 | }
|