1 |
|
2 |
|
3 | import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util';
|
4 | import { 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';
|
5 | import { decodePair } from "./decode.js";
|
6 | import { encodePair } from "./encode.js";
|
7 | import { pairToJson } from "./toJson.js";
|
8 | const SIG_TYPE_NONE = new Uint8Array();
|
9 | const TYPE_FROM_SEED = {
|
10 | ecdsa: secp256k1FromSeed,
|
11 | ed25519: ed25519FromSeed,
|
12 | ethereum: secp256k1FromSeed,
|
13 | sr25519: sr25519FromSeed
|
14 | };
|
15 | const TYPE_PREFIX = {
|
16 | ecdsa: new Uint8Array([2]),
|
17 | ed25519: new Uint8Array([0]),
|
18 | ethereum: new Uint8Array([2]),
|
19 | sr25519: new Uint8Array([1])
|
20 | };
|
21 | const 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 | };
|
27 | const 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 |
|
34 | function isLocked(secretKey) {
|
35 | return !secretKey || u8aEmpty(secretKey);
|
36 | }
|
37 |
|
38 | function vrfHash(proof, context, extra) {
|
39 | return blake2AsU8a(u8aConcat(context || '', extra || '', proof));
|
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 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 | export 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);
|
100 |
|
101 | encTypes = undefined;
|
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 |
|
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 |
|
202 |
|
203 |
|
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 |