Ethereum keypair signing & verification (#664)

* Ethereum keypair signing & verification

* Verify against hashes expanded

* Adjust via flags

* ss58 encoding for 33-byte publicKey

* ecdsa types stores address as raw publicKey

* Adjust for hash lengths

* Adjust expand/compress specs
This commit is contained in:
Jaco Greeff
2020-08-17 19:52:44 +02:00
committed by GitHub
parent 9808f58540
commit 8554d47030
18 changed files with 139 additions and 138 deletions
+32 -2
View File
@@ -228,13 +228,43 @@ describe('keypair', (): void => {
expect(pair.address).toEqual('0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
});
it('encodes a pair toJSON', (): void => {
const pair = keyring.createFromUri(PHRASE);
const json = pair.toJson('password');
expect(json.address).toEqual('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077');
expect(json.encoding).toEqual({
content: ['pkcs8', 'ethereum'],
type: ['scrypt', 'xsalsa20-poly1305'],
version: '3'
});
});
it('allows adding from JSON', (): void => {
const pair = keyring.addFromJson(
JSON.parse('{"address":"34wtVv7QQ5NsJQDcyWYnz9J5XrNgYE3VcRfKQ6scSt7o1ixi","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}')
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}')
);
expect(pair.publicKey).toEqual(hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
expect(pair.address).toEqual('0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
pair.decodePkcs8('password');
expect(pair.isLocked).toBe(false);
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
expect(pair.address).toBe('0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
});
it('allows for signing/verification', (): void => {
const MESSAGE = stringToU8a('just some test message');
const signer = keyring.createFromUri(PHRASE);
const verifier = keyring.addFromJson(
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}')
);
const signature = signer.sign(MESSAGE);
expect(verifier.verify(MESSAGE, signature)).toBe(true);
});
});
+2 -2
View File
@@ -8,7 +8,7 @@ import { PairInfo } from './types';
import { assert, stringToU8a, u8aFixLength } from '@polkadot/util';
import { naclDecrypt, scryptEncode, scryptFromU8a } from '@polkadot/util-crypto';
import { NONCE_LENGTH, PKCS8_DIVIDER, PKCS8_HEADER, PUB_LENGTH, SEC_LENGTH, SEED_LENGTH, SCRYPT_LENGTH } from './defaults';
import { ENCODING, NONCE_LENGTH, PKCS8_DIVIDER, PKCS8_HEADER, PUB_LENGTH, SEC_LENGTH, SEED_LENGTH, SCRYPT_LENGTH } from './defaults';
const SEED_OFFSET = PKCS8_HEADER.length;
@@ -43,7 +43,7 @@ function decodePkcs8 (encoded: Uint8Array): DecodeResult {
};
}
export default function decode (passphrase?: string, encrypted?: Uint8Array | null, encType: KeyringPair$JsonEncodingTypes[] = ['scrypt', 'xsalsa20-poly1305']): DecodeResult {
export default function decode (passphrase?: string, encrypted?: Uint8Array | null, encType: KeyringPair$JsonEncodingTypes[] = ENCODING): DecodeResult {
assert(encrypted, 'No encrypted data available to decode');
assert(passphrase || !encType.includes('xsalsa20-poly1305'), 'Password required to decode encypted data');
+4
View File
@@ -2,6 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringPair$JsonEncodingTypes } from '../types';
const ENCODING: KeyringPair$JsonEncodingTypes[] = ['scrypt', 'xsalsa20-poly1305'];
const NONCE_LENGTH = 24;
const PKCS8_DIVIDER = new Uint8Array([161, 35, 3, 33, 0]);
const PKCS8_HEADER = new Uint8Array([48, 83, 2, 1, 1, 48, 5, 6, 3, 43, 101, 112, 4, 34, 4, 32]);
@@ -12,6 +15,7 @@ const SEED_LENGTH = 32;
const SCRYPT_LENGTH = SALT_LENGTH + (3 * 4);
export {
ENCODING,
NONCE_LENGTH,
PKCS8_DIVIDER,
PKCS8_HEADER,
+36 -49
View File
@@ -6,8 +6,8 @@ import { Keypair, KeypairType } from '@polkadot/util-crypto/types';
import { KeyringPair, KeyringPair$Json, KeyringPair$JsonEncodingTypes, KeyringPair$Meta, SignOptions } from '../types';
import { PairInfo } from './types';
import { assert, u8aConcat } from '@polkadot/util';
import { blake2AsU8a, ethereumEncode, keccakAsU8a, keyExtractPath, keyFromPath, naclKeypairFromSeed as naclFromSeed, naclSign, naclVerify, schnorrkelKeypairFromSeed as schnorrkelFromSeed, schnorrkelSign, schnorrkelVerify, secp256k1Compress, secp256k1KeypairFromSeed as secp256k1FromSeed, secp256k1Sign, secp256k1Verify } from '@polkadot/util-crypto';
import { assert, u8aConcat, u8aToHex } from '@polkadot/util';
import { blake2AsU8a, ethereumEncode, keccakAsU8a, keyExtractPath, keyFromPath, naclKeypairFromSeed as naclFromSeed, naclSign, naclVerify, schnorrkelKeypairFromSeed as schnorrkelFromSeed, schnorrkelSign, schnorrkelVerify, secp256k1Expand, secp256k1KeypairFromSeed as secp256k1FromSeed, secp256k1Sign, secp256k1Verify, secp256k1Compress } from '@polkadot/util-crypto';
import decode from './decode';
import encode from './encode';
@@ -35,54 +35,28 @@ const TYPE_PREFIX = {
};
const TYPE_SIGNATURE = {
ecdsa: (message: Uint8Array, pair: Partial<Keypair>) =>
secp256k1Sign(message, pair, 'blake2'),
ecdsa: (m: Uint8Array, p: Partial<Keypair>) => secp256k1Sign(m, p, 'blake2'),
ed25519: naclSign,
ethereum: (message: Uint8Array, pair: Partial<Keypair>) =>
secp256k1Sign(message, pair, 'keccak'),
ethereum: (m: Uint8Array, p: Partial<Keypair>) => secp256k1Sign(m, p, 'keccak'),
sr25519: schnorrkelSign
};
const TYPE_ADDRESS = {
ecdsa: (p: Uint8Array) => p.length > 32 ? blake2AsU8a(p) : p,
ed25519: (p: Uint8Array) => p,
ethereum: (p: Uint8Array) => keccakAsU8a(secp256k1Expand(p)),
sr25519: (p: Uint8Array) => p
};
const TYPE_VERIFY = {
ecdsa: (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) =>
secp256k1Verify(message, signature, blake2AsU8a(publicKey), 'blake2'),
ecdsa: (m: Uint8Array, s: Uint8Array, p: Uint8Array) => secp256k1Verify(m, s, TYPE_ADDRESS.ecdsa(p), 'blake2'),
ed25519: naclVerify,
ethereum: (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) =>
secp256k1Verify(message, signature, keccakAsU8a(publicKey), 'keccak'),
ethereum: (m: Uint8Array, s: Uint8Array, p: Uint8Array) => secp256k1Verify(m, s, TYPE_ADDRESS.ethereum(p), 'keccak', true),
sr25519: schnorrkelVerify
};
function isEmpty (u8a: Uint8Array): boolean {
return u8a.reduce((count, u8): number => count + u8, 0) === 0;
}
function fromSeed (type: KeypairType, seed: Uint8Array): Keypair {
return TYPE_FROM_SEED[type](seed);
}
function sign (type: KeypairType, message: Uint8Array, pair: Partial<Keypair>, { withType = false }: SignOptions = {}): Uint8Array {
return u8aConcat(
// for multi-signatures, i.e. with indicator, append the signature type as per
// the MultiSignature enum
withType
? TYPE_PREFIX[type]
: SIG_TYPE_NONE,
TYPE_SIGNATURE[type](message, pair)
);
}
function verify (type: KeypairType, message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean {
return TYPE_VERIFY[type](message, signature, publicKey);
}
function getAddress (type: KeypairType, publicKey: Uint8Array, isCompressed = false): Uint8Array {
if (type === 'ecdsa' && publicKey.length > 32) {
return blake2AsU8a(publicKey);
} else if (type === 'ethereum' && isCompressed) {
return secp256k1Compress(publicKey).slice(-32);
}
return publicKey;
return u8a.reduce((count, u8) => count + u8, 0) === 0;
}
// Not 100% correct, since it can be a Uint8Array, but an invalid one - just say "undefined" is anything non-valid
@@ -129,11 +103,14 @@ export default function createPair ({ toSS58, type }: Setup, { publicKey, secret
return encoded;
};
const encodeAddress = (): string =>
type === 'ethereum'
? ethereumEncode(TYPE_ADDRESS[type](publicKey))
: toSS58(TYPE_ADDRESS[type](publicKey));
return {
get address (): string {
return type === 'ethereum'
? ethereumEncode(getAddress(type, publicKey))
: toSS58(getAddress(type, publicKey));
return encodeAddress();
},
get isLocked (): boolean {
return isLocked(secretKey);
@@ -155,7 +132,7 @@ export default function createPair ({ toSS58, type }: Setup, { publicKey, secret
publicKey = decoded.publicKey;
secretKey = decoded.secretKey;
} else {
const pair = fromSeed(type, decoded.secretKey);
const pair = TYPE_FROM_SEED[type](decoded.secretKey);
publicKey = pair.publicKey;
secretKey = pair.secretKey;
@@ -177,14 +154,24 @@ export default function createPair ({ toSS58, type }: Setup, { publicKey, secret
setMeta: (additional: KeyringPair$Meta): void => {
meta = { ...meta, ...additional };
},
sign: (message: Uint8Array, options?: SignOptions): Uint8Array => {
sign: (message: Uint8Array, options: SignOptions = {}): Uint8Array => {
assert(!isLocked(secretKey), 'Cannot sign with a locked key pair');
return sign(type, message, { publicKey, secretKey }, options);
return u8aConcat(
options.withType
? TYPE_PREFIX[type]
: SIG_TYPE_NONE,
TYPE_SIGNATURE[type](message, { publicKey, secretKey })
);
},
toJson: (passphrase?: string): KeyringPair$Json => {
const address = ['ecdsa', 'ethereum'].includes(type)
? u8aToHex(secp256k1Compress(publicKey))
: encodeAddress();
return toJson(type, { address, meta }, recode(passphrase), !!passphrase);
},
toJson: (passphrase?: string): KeyringPair$Json =>
toJson(type, { address: toSS58(getAddress(type, publicKey, true)), meta }, recode(passphrase), !!passphrase),
verify: (message: Uint8Array, signature: Uint8Array): boolean =>
verify(type, message, signature, publicKey)
TYPE_VERIFY[type](message, signature, publicKey)
};
}
+9 -4
View File
@@ -3,10 +3,12 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeypairType } from '@polkadot/util-crypto/types';
import { KeyringPair$Json, KeyringPair$Meta } from '../types';
import { KeyringPair$Json, KeyringPair$JsonEncodingTypes, KeyringPair$Meta } from '../types';
import { base64Encode } from '@polkadot/util-crypto';
import { ENCODING } from './defaults';
interface PairStateJson {
address: string;
meta: KeyringPair$Meta;
@@ -14,6 +16,9 @@ interface PairStateJson {
// version 2 - nonce, encoded (previous)
// version 3 - salt, nonce, encoded
const VERSION = '3';
const ENC_NONE: KeyringPair$JsonEncodingTypes[] = ['none'];
export default function toJson (type: KeypairType, { address, meta }: PairStateJson, encoded: Uint8Array, isEncrypted: boolean): KeyringPair$Json {
return {
@@ -22,9 +27,9 @@ export default function toJson (type: KeypairType, { address, meta }: PairStateJ
encoding: {
content: ['pkcs8', type],
type: isEncrypted
? ['scrypt', 'xsalsa20-poly1305']
: ['none'],
version: '3'
? ENCODING
: ENC_NONE,
version: VERSION
},
meta
};
@@ -5,7 +5,7 @@
import sshash from './sshash';
export default function checkChecksum (decoded: Uint8Array): [boolean, number] {
const isPublicKey = decoded.length === 35;
const isPublicKey = [35, 36].includes(decoded.length);
// non-publicKeys has 1 byte checksums, else default to 2
const length = decoded.length - (isPublicKey ? 2 : 1);
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { stringToU8a, u8aToHex } from '@polkadot/util';
import { stringToU8a, u8aToHex, hexToU8a } from '@polkadot/util';
import testingPairs, { TestKeyringMap } from '../../../keyring/src/testingPairs';
import decode from './decode';
@@ -74,6 +74,14 @@ describe('decodeAddress', (): void => {
).toEqual(new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]));
});
it('decodes a 33-byte address', (): void => {
expect(
decode('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou')
).toEqual(
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
);
});
it.skip('allows invalid prefix (in list)', (): void => {
expect(
(): Uint8Array => decode('6GfvWUvHvU8otbZ7sFhXH4eYeMcKdUkL61P3nFy52efEPVUx')
+2 -2
View File
@@ -5,9 +5,9 @@
import { allowedSS58 } from './ss58';
const defaults = {
allowedDecodedLengths: [1, 2, 4, 8, 32],
allowedDecodedLengths: [1, 2, 4, 8, 32, 33],
// publicKey has prefix + 2 checksum bytes, short only prefix + 1 checksum byte
allowedEncodedLengths: [3, 4, 6, 10, 35],
allowedEncodedLengths: [3, 4, 6, 10, 35, 36],
allowedPrefix: allowedSS58,
prefix: 42
};
@@ -67,11 +67,17 @@ describe('encode', (): void => {
).toEqual('as7QnGMf');
});
it('enodes a 8-byte address', (): void => {
it('encodes a 8-byte address', (): void => {
expect(
encode(
new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]), 68
)
).toEqual('4q7qY5RBG7Z4wv');
});
it('encodes an 33-byte address', (): void => {
expect(
encode('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
).toEqual('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou');
});
});
+1 -1
View File
@@ -19,7 +19,7 @@ export default function encode (_key: Uint8Array | string, ss58Format: Prefix =
assert(defaults.allowedDecodedLengths.includes(key.length), `Expected a valid key to convert, with length ${defaults.allowedDecodedLengths.join(', ')}`);
const isPublicKey = key.length === 32;
const isPublicKey = [32, 33].includes(key.length);
const input = u8aConcat(new Uint8Array([ss58Format]), key);
const hash = sshash(input);
@@ -2,6 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { keccakAsU8a } from '../keccak';
import { ethereumEncode } from './';
describe('formatAddress', () => {
@@ -35,12 +36,6 @@ describe('formatAddress', () => {
const ADDRESS = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
it('encodes a compressed publicKey', (): void => {
expect(
ethereumEncode('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
).toEqual(ADDRESS);
});
it('encodes a compressed publicKey (indicator)', (): void => {
expect(
ethereumEncode('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
).toEqual(ADDRESS);
@@ -48,13 +43,15 @@ describe('formatAddress', () => {
it('encodes an expanded publicKey', (): void => {
expect(
ethereumEncode('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
ethereumEncode('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
).toEqual(ADDRESS);
});
it('encodes an expanded publicKey (indicator + x/y)', (): void => {
it('encodes a pre-hashed key', (): void => {
expect(
ethereumEncode('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
ethereumEncode(
keccakAsU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
)
).toEqual(ADDRESS);
});
});
+11 -7
View File
@@ -7,20 +7,24 @@ import { assert, u8aToHex, u8aToU8a } from '@polkadot/util';
import { keccakAsU8a } from '../keccak';
import { secp256k1Expand } from '../secp256k1';
function getH160 (u8a: Uint8Array): Uint8Array {
if ([33, 65].includes(u8a.length)) {
u8a = keccakAsU8a(secp256k1Expand(u8a));
}
return u8a.slice(-20);
}
export default function ethereumEncode (addressOrPublic?: string | Uint8Array): string {
if (!addressOrPublic) {
return '0x';
}
let u8aAddress = u8aToU8a(addressOrPublic);
const u8aAddress = u8aToU8a(addressOrPublic);
assert([20, 32, 33, 64, 65].includes(u8aAddress.length), 'Invalid address or publicKey passed');
assert([20, 32, 33, 65].includes(u8aAddress.length), 'Invalid address or publicKey passed');
if (u8aAddress.length > 20) {
u8aAddress = keccakAsU8a(secp256k1Expand(u8aAddress)).slice(-20);
}
const address = u8aToHex(u8aAddress, -1, false);
const address = u8aToHex(getH160(u8aAddress), -1, false);
const hash = u8aToHex(keccakAsU8a(address), -1, false);
let result = '';
@@ -10,7 +10,7 @@ describe('secp256k1Compress', (): void => {
it('compresses a known key', (): void => {
expect(
secp256k1Compress(
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
hexToU8a('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
)
).toEqual(
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
@@ -3,16 +3,15 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import elliptic from 'elliptic';
import sanitizePublic from './sanitizePublic';
import { assert } from '@polkadot/util';
const EC = elliptic.ec;
const ec = new EC('secp256k1');
export default function secp256k1Compress (publicKey: Uint8Array): Uint8Array {
assert([33, 65].includes(publicKey.length), 'Invalid publicKey provided');
return new Uint8Array(
ec.keyFromPublic(
sanitizePublic(publicKey)
).getPublic().encodeCompressed()
ec.keyFromPublic(publicKey).getPublic().encodeCompressed()
);
}
@@ -17,16 +17,6 @@ describe('secp256k1Expand', (): void => {
);
});
it('expands a known key (no indicator)', (): void => {
expect(
secp256k1Expand(
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
)
).toEqual(
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
);
});
it('expands a known full key', (): void => {
expect(
secp256k1Expand(
@@ -36,14 +26,4 @@ describe('secp256k1Expand', (): void => {
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
);
});
it('expands a known full key (no indicator)', (): void => {
expect(
secp256k1Expand(
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
)
).toEqual(
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
);
});
});
+4 -4
View File
@@ -3,15 +3,15 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import elliptic from 'elliptic';
import { bnToU8a, u8aConcat } from '@polkadot/util';
import sanitizePublic from './sanitizePublic';
import { assert, bnToU8a, u8aConcat } from '@polkadot/util';
const EC = elliptic.ec;
const ec = new EC('secp256k1');
export default function secp256k1Expand (publicKey: Uint8Array): Uint8Array {
const expanded = ec.keyFromPublic(sanitizePublic(publicKey)).getPublic();
assert([33, 65].includes(publicKey.length), 'Invalid publicKey provided');
const expanded = ec.keyFromPublic(publicKey).getPublic();
return u8aConcat(
bnToU8a(expanded.getX(), { bitLength: 256, isLe: false }),
@@ -1,18 +0,0 @@
// Copyright 2017-2020 @polkadot/util-crypto authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { assert, u8aConcat } from '@polkadot/util';
const INDICATOR_COMP = new Uint8Array([3]);
const INDICATOR_FULL = new Uint8Array([4]);
export default function sanitizePublic (publicKey: Uint8Array): Uint8Array {
assert([32, 33, 64, 65].includes(publicKey.length), 'Invalid publicKey provided');
return [33, 65].includes(publicKey.length)
? publicKey
: publicKey.length === 32
? u8aConcat(INDICATOR_COMP, publicKey)
: u8aConcat(INDICATOR_FULL, publicKey);
}
+10 -11
View File
@@ -5,8 +5,9 @@
import { HashType } from './types';
import elliptic from 'elliptic';
import { assert, u8aToU8a } from '@polkadot/util';
import { assert, u8aEq, u8aToU8a } from '@polkadot/util';
import secp256k1Expand from './expand';
import hasher from './hasher';
const EC = elliptic.ec;
@@ -16,21 +17,19 @@ const ec = new EC('secp256k1');
* @name secp256k1Verify
* @description Verifies the signature of `message`, using the supplied pair
*/
export default function secp256k1Verify (message: Uint8Array | string, signature: Uint8Array | string, address: Uint8Array | string, hashType: HashType = 'blake2'): boolean {
const signatureU8a = u8aToU8a(signature);
export default function secp256k1Verify (message: Uint8Array | string, signature: Uint8Array | string, address: Uint8Array | string, hashType: HashType = 'blake2', isExpanded = false): boolean {
const u8a = u8aToU8a(signature);
assert(signatureU8a.length === 65, `Expected signature with 65 bytes, ${signatureU8a.length} found instead`);
assert(u8a.length === 65, `Expected signature with 65 bytes, ${u8a.length} found instead`);
const sig = {
r: signatureU8a.slice(0, 32),
s: signatureU8a.slice(32, 64)
};
const recovery = signatureU8a[64];
const publicKey = new Uint8Array(
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access
ec.recoverPubKey(hasher(hashType, message), sig, recovery)
ec.recoverPubKey(hasher(hashType, message), { r: u8a.slice(0, 32), s: u8a.slice(32, 64) }, u8a[64])
.encodeCompressed()
);
return Buffer.compare(hasher(hashType, publicKey), u8aToU8a(address)) === 0;
return u8aEq(
hasher(hashType, isExpanded ? secp256k1Expand(publicKey) : publicKey),
u8aToU8a(address)
);
}