1 | import { u8aIsWrapped, u8aToU8a, u8aUnwrapBytes, u8aWrapBytes } from '@polkadot/util';
|
2 | import { decodeAddress } from '../address/decode.js';
|
3 | import { ed25519Verify } from '../ed25519/verify.js';
|
4 | import { secp256k1Verify } from '../secp256k1/verify.js';
|
5 | import { sr25519Verify } from '../sr25519/verify.js';
|
6 | const secp256k1VerifyHasher = (hashType) => (message, signature, publicKey) => secp256k1Verify(message, signature, publicKey, hashType);
|
7 | const VERIFIERS_ECDSA = [
|
8 | ['ecdsa', secp256k1VerifyHasher('blake2')],
|
9 | ['ethereum', secp256k1VerifyHasher('keccak')]
|
10 | ];
|
11 | const VERIFIERS = [
|
12 | ['ed25519', ed25519Verify],
|
13 | ['sr25519', sr25519Verify],
|
14 | ...VERIFIERS_ECDSA
|
15 | ];
|
16 | const CRYPTO_TYPES = ['ed25519', 'sr25519', 'ecdsa'];
|
17 | function verifyDetect(result, { message, publicKey, signature }, verifiers = VERIFIERS) {
|
18 | result.isValid = verifiers.some(([crypto, verify]) => {
|
19 | try {
|
20 | if (verify(message, signature, publicKey)) {
|
21 | result.crypto = crypto;
|
22 | return true;
|
23 | }
|
24 | }
|
25 | catch {
|
26 |
|
27 | }
|
28 | return false;
|
29 | });
|
30 | return result;
|
31 | }
|
32 | function verifyMultisig(result, { message, publicKey, signature }) {
|
33 | if (![0, 1, 2].includes(signature[0])) {
|
34 | throw new Error(`Unknown crypto type, expected signature prefix [0..2], found ${signature[0]}`);
|
35 | }
|
36 | const type = CRYPTO_TYPES[signature[0]] || 'none';
|
37 | result.crypto = type;
|
38 | try {
|
39 | result.isValid = {
|
40 | ecdsa: () => verifyDetect(result, { message, publicKey, signature: signature.subarray(1) }, VERIFIERS_ECDSA).isValid,
|
41 | ed25519: () => ed25519Verify(message, signature.subarray(1), publicKey),
|
42 | none: () => {
|
43 | throw Error('no verify for `none` crypto type');
|
44 | },
|
45 | sr25519: () => sr25519Verify(message, signature.subarray(1), publicKey)
|
46 | }[type]();
|
47 | }
|
48 | catch {
|
49 |
|
50 | }
|
51 | return result;
|
52 | }
|
53 | function getVerifyFn(signature) {
|
54 | return [0, 1, 2].includes(signature[0]) && [65, 66].includes(signature.length)
|
55 | ? verifyMultisig
|
56 | : verifyDetect;
|
57 | }
|
58 | export function signatureVerify(message, signature, addressOrPublicKey) {
|
59 | const signatureU8a = u8aToU8a(signature);
|
60 | if (![64, 65, 66].includes(signatureU8a.length)) {
|
61 | throw new Error(`Invalid signature length, expected [64..66] bytes, found ${signatureU8a.length}`);
|
62 | }
|
63 | const publicKey = decodeAddress(addressOrPublicKey);
|
64 | const input = { message: u8aToU8a(message), publicKey, signature: signatureU8a };
|
65 | const result = { crypto: 'none', isValid: false, isWrapped: u8aIsWrapped(input.message, true), publicKey };
|
66 | const isWrappedBytes = u8aIsWrapped(input.message, false);
|
67 | const verifyFn = getVerifyFn(signatureU8a);
|
68 | verifyFn(result, input);
|
69 | if (result.crypto !== 'none' || (result.isWrapped && !isWrappedBytes)) {
|
70 | return result;
|
71 | }
|
72 | input.message = isWrappedBytes
|
73 | ? u8aUnwrapBytes(input.message)
|
74 | : u8aWrapBytes(input.message);
|
75 | return verifyFn(result, input);
|
76 | }
|