Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9808f58540 | ||
|
|
7228100835 | ||
|
|
08c1b413d2 | ||
|
|
27ae11860d | ||
|
|
9cc984e8ae | ||
|
|
d352782927 | ||
|
|
6e4a5281ed | ||
|
|
d11f6d9307 | ||
|
|
61b57687af | ||
|
|
3ab1623a40 | ||
|
|
f5acd60253 | ||
|
|
c4d368d2b5 | ||
|
|
e487d0a454 |
@@ -1,5 +1,12 @@
|
||||
# CHANGELOG
|
||||
|
||||
## 3.2.1 Aug 17, 2020
|
||||
|
||||
- Allow for (optional, default blake2) hasher to secp256k1 sign/verify functions
|
||||
- Utilities for secp256k1 publicKey expansion and compression
|
||||
- Add basic Ethereum address utils (encode with checksum & checksum verification)
|
||||
- Add Ethereum-compatible `mnemonicToBip39` seed creation function
|
||||
|
||||
## 3.1.1 Aug 10, 2020
|
||||
|
||||
- Add `deriveAddress(<address>, '/<soft>/<soft-b>')` via sr25519 soft
|
||||
|
||||
+1
-1
@@ -9,5 +9,5 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "3.1.1"
|
||||
"version": "3.2.1"
|
||||
}
|
||||
|
||||
+3
-3
@@ -22,9 +22,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.11.1",
|
||||
"@polkadot/dev": "^0.55.36",
|
||||
"@polkadot/ts": "^0.3.36",
|
||||
"@polkadot/dev": "^0.55.38",
|
||||
"@polkadot/ts": "^0.3.38",
|
||||
"@types/jest": "^26.0.9"
|
||||
},
|
||||
"version": "3.1.1"
|
||||
"version": "3.2.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@polkadot/keyring",
|
||||
"version": "3.1.1",
|
||||
"version": "3.2.1",
|
||||
"description": "Keyring management",
|
||||
"main": "index.js",
|
||||
"publishConfig": {
|
||||
@@ -28,7 +28,7 @@
|
||||
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/keyring#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@polkadot/util": "3.1.1",
|
||||
"@polkadot/util-crypto": "3.1.1"
|
||||
"@polkadot/util": "3.2.1",
|
||||
"@polkadot/util-crypto": "3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,31 @@ describe('keypair', (): void => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('ethereum', (): void => {
|
||||
const PHRASE = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ type: 'ethereum' });
|
||||
});
|
||||
|
||||
it('adds a pair with the correct address', (): void => {
|
||||
const pair = keyring.createFromUri(PHRASE);
|
||||
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
|
||||
expect(pair.address).toEqual('0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
});
|
||||
|
||||
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":{}}')
|
||||
);
|
||||
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
|
||||
expect(pair.address).toEqual('0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
});
|
||||
});
|
||||
|
||||
describe('util', (): void => {
|
||||
let keyring: Keyring;
|
||||
|
||||
|
||||
@@ -6,12 +6,19 @@ import { KeypairType, Keypair } from '@polkadot/util-crypto/types';
|
||||
import { KeyringInstance, KeyringPair, KeyringPair$Json, KeyringPair$JsonEncodingTypes, KeyringPair$Meta, KeyringOptions } from './types';
|
||||
|
||||
import { assert, hexToU8a, isHex, isUndefined, stringToU8a } from '@polkadot/util';
|
||||
import { base64Decode, decodeAddress, encodeAddress, keyExtractSuri, keyFromPath, naclKeypairFromSeed as naclFromSeed, schnorrkelKeypairFromSeed as schnorrkelFromSeed, secp256k1KeypairFromSeed as secp256k1FromSeed, mnemonicToMiniSecret } from '@polkadot/util-crypto';
|
||||
import { base64Decode, decodeAddress, encodeAddress, keyExtractSuri, keyFromPath, naclKeypairFromSeed as naclFromSeed, schnorrkelKeypairFromSeed as schnorrkelFromSeed, secp256k1KeypairFromSeed as secp256k1FromSeed, mnemonicToBip39, mnemonicToMiniSecret } from '@polkadot/util-crypto';
|
||||
|
||||
import { DEV_PHRASE } from './defaults';
|
||||
import createPair from './pair';
|
||||
import Pairs from './pairs';
|
||||
|
||||
const keypairFromSeed = {
|
||||
ecdsa: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
|
||||
ed25519: (seed: Uint8Array): Keypair => naclFromSeed(seed),
|
||||
ethereum: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
|
||||
sr25519: (seed: Uint8Array): Keypair => schnorrkelFromSeed(seed)
|
||||
};
|
||||
|
||||
/**
|
||||
* # @polkadot/keyring
|
||||
*
|
||||
@@ -40,7 +47,7 @@ export default class Keyring implements KeyringInstance {
|
||||
constructor (options: KeyringOptions = {}) {
|
||||
options.type = options.type || 'ed25519';
|
||||
|
||||
assert(options && ['ecdsa', 'ed25519', 'sr25519'].includes(options.type || 'undefined'), `Expected a keyring type of either 'ed25519', 'sr25519' or 'ecdsa', found '${options.type}`);
|
||||
assert(options && ['ecdsa', 'ethereum', 'ed25519', 'sr25519'].includes(options.type || 'undefined'), `Expected a keyring type of either 'ed25519', 'sr25519' or 'ecdsa', found '${options.type}`);
|
||||
|
||||
this.#pairs = new Pairs();
|
||||
this.#ss58 = options.ss58Format;
|
||||
@@ -128,13 +135,7 @@ export default class Keyring implements KeyringInstance {
|
||||
* `addPair` to store in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
|
||||
*/
|
||||
public addFromSeed (seed: Uint8Array, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||
const keypair = {
|
||||
ecdsa: (): Keypair => secp256k1FromSeed(seed),
|
||||
ed25519: (): Keypair => naclFromSeed(seed),
|
||||
sr25519: (): Keypair => schnorrkelFromSeed(seed)
|
||||
}[type]();
|
||||
|
||||
return this.addPair(createPair({ toSS58: this.encodeAddress, type }, keypair, meta, null));
|
||||
return this.addPair(createPair({ toSS58: this.encodeAddress, type }, keypairFromSeed[type](seed), meta, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,11 +169,9 @@ export default class Keyring implements KeyringInstance {
|
||||
const parts = str.split(' ');
|
||||
|
||||
if ([12, 15, 18, 21, 24].includes(parts.length)) {
|
||||
// FIXME This keeps compat with older versions, but breaks compat with subkey
|
||||
// seed = type === 'sr25519'
|
||||
// ? mnemonicToMiniSecret(phrase, password)
|
||||
// : mnemonicToSeed(phrase, password);
|
||||
seed = mnemonicToMiniSecret(phrase, password);
|
||||
seed = type === 'ethereum'
|
||||
? mnemonicToBip39(phrase)
|
||||
: mnemonicToMiniSecret(phrase, password);
|
||||
} else {
|
||||
assert(str.length <= 32, 'specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');
|
||||
|
||||
@@ -180,12 +179,8 @@ export default class Keyring implements KeyringInstance {
|
||||
}
|
||||
}
|
||||
|
||||
const keypair = {
|
||||
ecdsa: (): Keypair => secp256k1FromSeed(seed),
|
||||
ed25519: (): Keypair => naclFromSeed(seed),
|
||||
sr25519: (): Keypair => schnorrkelFromSeed(seed)
|
||||
};
|
||||
const derived = keyFromPath(keypair[type](), path, type);
|
||||
// FIXME Need to support Ethereum-type derivation paths
|
||||
const derived = keyFromPath(keypairFromSeed[type](seed), path, type);
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type }, derived, meta, null);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
import { cryptoWaitReady, encodeAddress as toSS58, setSS58Format } from '@polkadot/util-crypto';
|
||||
|
||||
import { hexToU8a } from '@polkadot/util';
|
||||
|
||||
import { PAIRS } from '../testing';
|
||||
import testingPairs from '../testingPairs';
|
||||
import createPair from '.';
|
||||
@@ -103,4 +105,12 @@ describe('pair', (): void => {
|
||||
pair.sign(new Uint8Array([0]))
|
||||
).toThrow('Cannot sign with a locked key pair');
|
||||
});
|
||||
|
||||
describe('ethereum', (): void => {
|
||||
it('has a valid address from a known public', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077') });
|
||||
|
||||
expect(pair.address).toEqual('0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { KeyringPair, KeyringPair$Json, KeyringPair$JsonEncodingTypes, KeyringPa
|
||||
import { PairInfo } from './types';
|
||||
|
||||
import { assert, u8aConcat } from '@polkadot/util';
|
||||
import { keyExtractPath, keyFromPath, naclKeypairFromSeed as naclFromSeed, naclSign, naclVerify, schnorrkelKeypairFromSeed as schnorrkelFromSeed, schnorrkelSign, schnorrkelVerify, secp256k1KeypairFromSeed as secp256k1FromSeed, secp256k1Sign, secp256k1Verify, blake2AsU8a } from '@polkadot/util-crypto';
|
||||
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 decode from './decode';
|
||||
import encode from './encode';
|
||||
@@ -19,61 +19,70 @@ interface Setup {
|
||||
}
|
||||
|
||||
const SIG_TYPE_NONE = new Uint8Array();
|
||||
const SIG_TYPE_ED25519 = new Uint8Array([0]);
|
||||
const SIG_TYPE_SR25519 = new Uint8Array([1]);
|
||||
const SIG_TYPE_ECDSA = new Uint8Array([2]);
|
||||
|
||||
const TYPE_FROM_SEED = {
|
||||
ecdsa: secp256k1FromSeed,
|
||||
ed25519: naclFromSeed,
|
||||
ethereum: secp256k1FromSeed,
|
||||
sr25519: schnorrkelFromSeed
|
||||
};
|
||||
|
||||
const TYPE_PREFIX = {
|
||||
ecdsa: new Uint8Array([2]),
|
||||
ed25519: new Uint8Array([0]),
|
||||
ethereum: new Uint8Array([2]),
|
||||
sr25519: new Uint8Array([1])
|
||||
};
|
||||
|
||||
const TYPE_SIGNATURE = {
|
||||
ecdsa: (message: Uint8Array, pair: Partial<Keypair>) =>
|
||||
secp256k1Sign(message, pair, 'blake2'),
|
||||
ed25519: naclSign,
|
||||
ethereum: (message: Uint8Array, pair: Partial<Keypair>) =>
|
||||
secp256k1Sign(message, pair, 'keccak'),
|
||||
sr25519: schnorrkelSign
|
||||
};
|
||||
|
||||
const TYPE_VERIFY = {
|
||||
ecdsa: (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) =>
|
||||
secp256k1Verify(message, signature, blake2AsU8a(publicKey), 'blake2'),
|
||||
ed25519: naclVerify,
|
||||
ethereum: (message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) =>
|
||||
secp256k1Verify(message, signature, keccakAsU8a(publicKey), 'keccak'),
|
||||
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 {
|
||||
ecdsa: (): Keypair => secp256k1FromSeed(seed),
|
||||
ed25519: (): Keypair => naclFromSeed(seed),
|
||||
sr25519: (): Keypair => schnorrkelFromSeed(seed)
|
||||
}[type]();
|
||||
}
|
||||
|
||||
function multiSignaturePrefix (type: KeypairType): Uint8Array {
|
||||
return {
|
||||
ecdsa: SIG_TYPE_ECDSA,
|
||||
ed25519: SIG_TYPE_ED25519,
|
||||
sr25519: SIG_TYPE_SR25519
|
||||
}[type];
|
||||
return TYPE_FROM_SEED[type](seed);
|
||||
}
|
||||
|
||||
function sign (type: KeypairType, message: Uint8Array, pair: Partial<Keypair>, { withType = false }: SignOptions = {}): Uint8Array {
|
||||
const signature = {
|
||||
ecdsa: (): Uint8Array => secp256k1Sign(message, pair),
|
||||
ed25519: (): Uint8Array => naclSign(message, pair),
|
||||
sr25519: (): Uint8Array => schnorrkelSign(message, pair)
|
||||
}[type]();
|
||||
|
||||
return u8aConcat(
|
||||
// for multi-signatures, i.e. with indicator, append the signature type as per
|
||||
// the MultiSignature enum
|
||||
withType
|
||||
? multiSignaturePrefix(type)
|
||||
? TYPE_PREFIX[type]
|
||||
: SIG_TYPE_NONE,
|
||||
signature
|
||||
TYPE_SIGNATURE[type](message, pair)
|
||||
);
|
||||
}
|
||||
|
||||
function verify (type: KeypairType, message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array): boolean {
|
||||
return {
|
||||
ecdsa: (): boolean => secp256k1Verify(message, signature, blake2AsU8a(publicKey, 256)),
|
||||
ed25519: (): boolean => naclVerify(message, signature, publicKey),
|
||||
sr25519: (): boolean => schnorrkelVerify(message, signature, publicKey)
|
||||
}[type]();
|
||||
return TYPE_VERIFY[type](message, signature, publicKey);
|
||||
}
|
||||
|
||||
function getAddress (type: KeypairType, publicKey: Uint8Array): Uint8Array {
|
||||
function getAddress (type: KeypairType, publicKey: Uint8Array, isCompressed = false): Uint8Array {
|
||||
if (type === 'ecdsa' && publicKey.length > 32) {
|
||||
return blake2AsU8a(publicKey, 256);
|
||||
} else {
|
||||
return publicKey;
|
||||
return blake2AsU8a(publicKey);
|
||||
} else if (type === 'ethereum' && isCompressed) {
|
||||
return secp256k1Compress(publicKey).slice(-32);
|
||||
}
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
// Not 100% correct, since it can be a Uint8Array, but an invalid one - just say "undefined" is anything non-valid
|
||||
@@ -122,7 +131,9 @@ export default function createPair ({ toSS58, type }: Setup, { publicKey, secret
|
||||
|
||||
return {
|
||||
get address (): string {
|
||||
return toSS58(getAddress(type, publicKey));
|
||||
return type === 'ethereum'
|
||||
? ethereumEncode(getAddress(type, publicKey))
|
||||
: toSS58(getAddress(type, publicKey));
|
||||
},
|
||||
get isLocked (): boolean {
|
||||
return isLocked(secretKey);
|
||||
@@ -161,7 +172,7 @@ export default function createPair ({ toSS58, type }: Setup, { publicKey, secret
|
||||
encodePkcs8: (passphrase?: string): Uint8Array =>
|
||||
recode(passphrase),
|
||||
lock: (): void => {
|
||||
secretKey = new Uint8Array(0);
|
||||
secretKey = new Uint8Array();
|
||||
},
|
||||
setMeta: (additional: KeyringPair$Meta): void => {
|
||||
meta = { ...meta, ...additional };
|
||||
@@ -172,7 +183,7 @@ export default function createPair ({ toSS58, type }: Setup, { publicKey, secret
|
||||
return sign(type, message, { publicKey, secretKey }, options);
|
||||
},
|
||||
toJson: (passphrase?: string): KeyringPair$Json =>
|
||||
toJson(type, { address: toSS58(getAddress(type, publicKey)), meta }, recode(passphrase), !!passphrase),
|
||||
toJson(type, { address: toSS58(getAddress(type, publicKey, true)), meta }, recode(passphrase), !!passphrase),
|
||||
verify: (message: Uint8Array, signature: Uint8Array): boolean =>
|
||||
verify(type, message, signature, publicKey)
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@polkadot/util-crypto",
|
||||
"version": "3.1.1",
|
||||
"version": "3.2.1",
|
||||
"description": "A collection of useful crypto utilities for @polkadot",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
@@ -26,7 +26,7 @@
|
||||
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/util-crypto#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@polkadot/util": "3.1.1",
|
||||
"@polkadot/util": "3.2.1",
|
||||
"@polkadot/wasm-crypto": "^1.3.1",
|
||||
"base-x": "^3.0.8",
|
||||
"bip39": "^3.0.2",
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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 { ethereumEncode } from './';
|
||||
|
||||
describe('formatAddress', () => {
|
||||
describe('address to address encoding', (): void => {
|
||||
const ADDRESS = '0x00a329c0648769A73afAc7F9381E08FB43dBEA72';
|
||||
|
||||
it('returns 0x for no address', () => {
|
||||
expect(ethereumEncode()).toBe('0x');
|
||||
});
|
||||
|
||||
it('returns fails on invalid address', () => {
|
||||
expect(
|
||||
() => ethereumEncode('0xnotaddress')
|
||||
).toThrow(/Invalid address or publicKey passed/);
|
||||
});
|
||||
|
||||
it('converts lowercase to the checksummed address', () => {
|
||||
expect(ethereumEncode(ADDRESS.toLowerCase())).toBe(ADDRESS);
|
||||
});
|
||||
|
||||
it('converts uppercase to the checksummed address', () => {
|
||||
expect(ethereumEncode(ADDRESS.toUpperCase().replace('0X', '0x'))).toBe(ADDRESS);
|
||||
});
|
||||
|
||||
it('returns formatted address on checksum input', () => {
|
||||
expect(ethereumEncode(ADDRESS)).toBe(ADDRESS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('from publicKey', (): void => {
|
||||
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);
|
||||
});
|
||||
|
||||
it('encodes an expanded publicKey', (): void => {
|
||||
expect(
|
||||
ethereumEncode('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
).toEqual(ADDRESS);
|
||||
});
|
||||
|
||||
it('encodes an expanded publicKey (indicator + x/y)', (): void => {
|
||||
expect(
|
||||
ethereumEncode('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
).toEqual(ADDRESS);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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, u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
import { keccakAsU8a } from '../keccak';
|
||||
import { secp256k1Expand } from '../secp256k1';
|
||||
|
||||
export default function ethereumEncode (addressOrPublic?: string | Uint8Array): string {
|
||||
if (!addressOrPublic) {
|
||||
return '0x';
|
||||
}
|
||||
|
||||
let u8aAddress = u8aToU8a(addressOrPublic);
|
||||
|
||||
assert([20, 32, 33, 64, 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 hash = u8aToHex(keccakAsU8a(address), -1, false);
|
||||
let result = '';
|
||||
|
||||
for (let index = 0; index < 40; index++) {
|
||||
result = `${result}${parseInt(hash[index], 16) > 7 ? address[index].toUpperCase() : address[index]}`;
|
||||
}
|
||||
|
||||
return `0x${result}`;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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.
|
||||
|
||||
export { default as ethereumEncode } from './encode';
|
||||
export { default as isEthereumAddress } from './isAddress';
|
||||
export { default as isEthereumChecksum } from './isChecksum';
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 { isEthereumAddress } from './';
|
||||
|
||||
const ADDRESS = '0x00a329c0648769A73afAc7F9381E08FB43dBEA72';
|
||||
|
||||
describe('isEthereumAddress', () => {
|
||||
it('returns true when fully lowercase', () => {
|
||||
expect(isEthereumAddress(ADDRESS.toLowerCase())).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when fully uppercase', () => {
|
||||
expect(isEthereumAddress(ADDRESS.toUpperCase().replace('0X', '0x'))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when checksummed', () => {
|
||||
expect(isEthereumAddress(ADDRESS)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when empty address', () => {
|
||||
expect(isEthereumAddress()).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when invalid address', () => {
|
||||
expect(isEthereumAddress('0xinvalid')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when invalid address of correct length', () => {
|
||||
expect(isEthereumAddress('0xinvalid000123456789012345678901234567890')).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 { isHex } from '@polkadot/util';
|
||||
|
||||
import isEthereumChecksum from './isChecksum';
|
||||
|
||||
export default function isEthereumAddress (address?: string): boolean {
|
||||
if (!address || address.length !== 42 || !isHex(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isEthereumChecksum(address);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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 { u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { keccakAsU8a } from '../keccak';
|
||||
|
||||
export default function isEthereumChecksum (_address: string): boolean {
|
||||
const address = _address.replace('0x', '');
|
||||
const hash = u8aToHex(keccakAsU8a(address.toLowerCase()), -1, false);
|
||||
|
||||
for (let index = 0; index < 40; index++) {
|
||||
const char = address[index];
|
||||
const hashval = parseInt(hash[index], 16);
|
||||
|
||||
if ((hashval > 7 && char !== char.toUpperCase()) || (hashval <= 7 && char !== char.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 { isEthereumChecksum } from './';
|
||||
|
||||
const ADDRESS = '0x00a329c0648769A73afAc7F9381E08FB43dBEA72';
|
||||
|
||||
describe('isEthereumChecksum', () => {
|
||||
it('returns false on invalid address', () => {
|
||||
expect(isEthereumChecksum('0xinvalid')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false on non-checksum address', () => {
|
||||
expect(isEthereumChecksum('0x1234567890abcdeedcba1234567890abcdeedcba')).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when fully lowercase', () => {
|
||||
expect(isEthereumChecksum(ADDRESS.toLowerCase())).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when fully uppercase', () => {
|
||||
expect(isEthereumChecksum(ADDRESS.toUpperCase().replace('0X', '0x'))).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true on a checksummed address', () => {
|
||||
expect(isEthereumChecksum(ADDRESS)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -10,9 +10,11 @@ export * from './init';
|
||||
|
||||
// all internal exports
|
||||
export * from './address';
|
||||
export * from './base32';
|
||||
export * from './base58';
|
||||
export * from './base64';
|
||||
export * from './blake2';
|
||||
export * from './ethereum';
|
||||
export * from './keccak';
|
||||
export * from './key';
|
||||
export * from './mnemonic';
|
||||
|
||||
@@ -24,6 +24,6 @@ export default function keccakAsU8a (value: Buffer | Uint8Array | string): Uint8
|
||||
return isReady()
|
||||
? keccak256(u8aToU8a(value))
|
||||
: new Uint8Array(
|
||||
jssha3.keccak256.update(value).arrayBuffer()
|
||||
jssha3.keccak256.update(u8aToU8a(value)).arrayBuffer()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import keyHdkdEcdsa from './hdkdEcdsa';
|
||||
const generators = {
|
||||
ecdsa: keyHdkdEcdsa,
|
||||
ed25519: keyHdkdEd15519,
|
||||
// FIXME This is Substrate-compatible, not Ethereum-compatible
|
||||
ethereum: keyHdkdEcdsa,
|
||||
sr25519: keyHdkdSr15519
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../polyfill';
|
||||
/**
|
||||
* @summary Create valid mnemonic strings, validate them using BIP39, and convert them to valid seeds
|
||||
*/
|
||||
|
||||
export { default as mnemonicToBip39 } from './toBip39';
|
||||
export { default as mnemonicGenerate } from './generate';
|
||||
export { default as mnemonicToEntropy } from './toEntropy';
|
||||
export { default as mnemonicToMiniSecret } from './toMiniSecret';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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 { u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { mnemonicToBip39 } from './';
|
||||
|
||||
describe('mnemonicToBip39Seed', (): void => {
|
||||
it('generates a valid seed', (): void => {
|
||||
expect(
|
||||
u8aToHex(
|
||||
mnemonicToBip39('seed sock milk update focus rotate barely fade car face mechanic mercy')
|
||||
)
|
||||
).toEqual('0x3c121e20de068083b49c2315697fb59a2d9e8643c24e5ea7628132c58969a027');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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 { mnemonicToSeedSync } from 'bip39';
|
||||
import { bufferToU8a } from '@polkadot/util';
|
||||
|
||||
// import { bip39ToSeed, isReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
/**
|
||||
* @name toSeed
|
||||
* @summary Creates a valid Ethereum/Bitcoin-compatible seed from a mnemonic input
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { mnemonicGenerate, mnemonicToBip39, mnemonicValidate } from '@polkadot/util-crypto';
|
||||
*
|
||||
* const mnemonic = mnemonicGenerate(); // => string
|
||||
* const isValidMnemonic = mnemonicValidate(mnemonic); // => boolean
|
||||
*
|
||||
* if (isValidMnemonic) {
|
||||
* console.log(`Seed generated from mnemonic: ${mnemonicToBip39(mnemonic)}`); => u8a
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export default function toBip39 (mnemonic: string, password = ''): Uint8Array {
|
||||
// FIXMe Re-add when we have support in the WASM libs again
|
||||
// return isReady()
|
||||
// ? bip39ToSeed(mnemonic, password)
|
||||
|
||||
return bufferToU8a(mnemonicToSeedSync(mnemonic, password)).subarray(0, 32);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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 { hexToU8a } from '@polkadot/util';
|
||||
|
||||
import { secp256k1Compress } from './';
|
||||
|
||||
describe('secp256k1Compress', (): void => {
|
||||
it('compresses a known key', (): void => {
|
||||
expect(
|
||||
secp256k1Compress(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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 elliptic from 'elliptic';
|
||||
|
||||
import sanitizePublic from './sanitizePublic';
|
||||
|
||||
const EC = elliptic.ec;
|
||||
const ec = new EC('secp256k1');
|
||||
|
||||
export default function secp256k1Compress (publicKey: Uint8Array): Uint8Array {
|
||||
return new Uint8Array(
|
||||
ec.keyFromPublic(
|
||||
sanitizePublic(publicKey)
|
||||
).getPublic().encodeCompressed()
|
||||
);
|
||||
}
|
||||
@@ -9,5 +9,6 @@ import blake2AsU8a from '../blake2/asU8a';
|
||||
const HDKD = compactAddLength(stringToU8a('Secp256k1HDKD'));
|
||||
|
||||
export default function deriveHard (seed: Uint8Array, chainCode: Uint8Array): Uint8Array {
|
||||
// NOTE This is specific to the Substrate HDD derivation, so always use the blake2 hasher
|
||||
return blake2AsU8a(u8aConcat(HDKD, seed, chainCode), 256);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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 { hexToU8a } from '@polkadot/util';
|
||||
|
||||
import { secp256k1Expand } from './';
|
||||
|
||||
describe('secp256k1Expand', (): void => {
|
||||
it('expands a known key', (): void => {
|
||||
expect(
|
||||
secp256k1Expand(
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
);
|
||||
});
|
||||
|
||||
it('expands a known key (no indicator)', (): void => {
|
||||
expect(
|
||||
secp256k1Expand(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
);
|
||||
});
|
||||
|
||||
it('expands a known full key', (): void => {
|
||||
expect(
|
||||
secp256k1Expand(
|
||||
hexToU8a('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
);
|
||||
});
|
||||
|
||||
it('expands a known full key (no indicator)', (): void => {
|
||||
expect(
|
||||
secp256k1Expand(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 elliptic from 'elliptic';
|
||||
import { bnToU8a, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import sanitizePublic from './sanitizePublic';
|
||||
|
||||
const EC = elliptic.ec;
|
||||
const ec = new EC('secp256k1');
|
||||
|
||||
export default function secp256k1Expand (publicKey: Uint8Array): Uint8Array {
|
||||
const expanded = ec.keyFromPublic(sanitizePublic(publicKey)).getPublic();
|
||||
|
||||
return u8aConcat(
|
||||
bnToU8a(expanded.getX(), { bitLength: 256, isLe: false }),
|
||||
bnToU8a(expanded.getY(), { bitLength: 256, isLe: false })
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 hasher from './hasher';
|
||||
|
||||
describe('secp256k1Hasher', (): void => {
|
||||
it('fails with unknown hasher', (): void => {
|
||||
expect(
|
||||
() => hasher('unknown' as 'blake2', 'testing')
|
||||
).toThrow(/Unsupported secp256k1 hasher 'unknown', expected one of blake2, keccak/);
|
||||
});
|
||||
|
||||
it('creates a blake2 hash', (): void => {
|
||||
expect(
|
||||
hasher('blake2', 'abc')
|
||||
).toEqual(
|
||||
new Uint8Array([189, 221, 129, 60, 99, 66, 57, 114, 49, 113, 239, 63, 238, 152, 87, 155, 148, 150, 78, 59, 177, 203, 62, 66, 114, 98, 200, 192, 104, 213, 35, 25])
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a keccak hash', (): void => {
|
||||
expect(
|
||||
hasher('keccak', 'abc')
|
||||
).toEqual(
|
||||
new Uint8Array([78, 3, 101, 122, 234, 69, 169, 79, 199, 212, 123, 168, 38, 200, 214, 103, 192, 209, 230, 227, 58, 100, 160, 54, 236, 68, 245, 143, 161, 45, 108, 69])
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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 { HashType } from './types';
|
||||
|
||||
import { blake2AsU8a } from '../blake2';
|
||||
import { keccakAsU8a } from '../keccak';
|
||||
|
||||
const HASH_TYPES = ['blake2', 'keccak'];
|
||||
|
||||
export default function secp256k1Hasher (hashType: HashType, data: Uint8Array | string): Uint8Array {
|
||||
if (hashType === 'blake2') {
|
||||
return blake2AsU8a(data);
|
||||
} else if (hashType === 'keccak') {
|
||||
return keccakAsU8a(data);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported secp256k1 hasher '${hashType as string}', expected one of ${HASH_TYPES.join(', ')}`);
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
export { default as secp256k1Compress } from './compress';
|
||||
export { default as secp256k1Expand } from './expand';
|
||||
export { default as secp256k1KeypairFromSeed } from './keypair/fromSeed';
|
||||
export { default as secp256k1Recover } from './recover';
|
||||
export { default as secp256k1Verify } from './verify';
|
||||
|
||||
@@ -6,7 +6,36 @@ import { u8aToHex, hexToU8a } from '@polkadot/util';
|
||||
|
||||
import { mnemonicToMiniSecret } from '../../mnemonic';
|
||||
import { secp256k1KeypairFromSeed } from '..';
|
||||
import tests from './testing';
|
||||
|
||||
// mnemonic, secret, public, account_id
|
||||
type Test = [string, string, string, string];
|
||||
|
||||
const tests: Test[] = [
|
||||
[
|
||||
'life fee table ahead modify maximum dumb such tobacco boss dry nurse',
|
||||
'0xf2360e871c830d397fe221382b503f07ddd8763df81a94bb2504390a2fb91f59',
|
||||
'0x036b0aa6beab469dd2b748a0ff5ddbe3d13df1e15c9d28a2aa057212994e127bea',
|
||||
'0xae8e8fcacbaeb607bcdf0bbd7e615f2b4ef484ee54f19d68a7393fb6db2dd9cd'
|
||||
],
|
||||
[
|
||||
'tide survey cradle cover column ugly author wait eye state elder blame',
|
||||
'0x5385355a5118ec732b9dbcf1668ba21db38b07cf79082dafa9a7cc4b52e4abb0',
|
||||
'0x03929e4f93cdad265751ad8f6365185d8e937610d19b510400f5867d542d60a313',
|
||||
'0xf80ea815da66c42f870b687e1530770d5a7936ae81a147b009506d85bd6d621c'
|
||||
],
|
||||
[
|
||||
'laugh fish flee cake approve butter april dynamic myth license ticket lobster',
|
||||
'0x83ec65cf9a8a7442d808aef6f8987599f1ba3be880769bb3a20621b13adbd476',
|
||||
'0x0388299e4cfaa33d180a026bd54a46ad98df129a131320a9d2fd6f80e64bc3db39',
|
||||
'0x35036238dd195f4c2169379354bda6cba5746f67bde03ef59a77a4cea80729bc'
|
||||
],
|
||||
[
|
||||
'animal thing fork recipe exotic pilot inquiry pledge obey slab obtain reveal',
|
||||
'0x0fd50580eb5a58b0eee60c77656dffa50094b539262366f1227d3babfd7343e5',
|
||||
'0x036edc954685ad89f0a23b0fb1eb2b9c3a8600eee9091c758426dfb2bc7889a7c3',
|
||||
'0x2a94b10d1f28810dc4628e7e424b2d08bd3d17fb08f9416d112f17e86c8fa77c'
|
||||
]
|
||||
];
|
||||
|
||||
describe('secp256k1KeypairFromSeed', (): void => {
|
||||
const TEST = hexToU8a('0x4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae');
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Keypair } from '../../types';
|
||||
import { assert, hexToU8a } from '@polkadot/util';
|
||||
|
||||
import elliptic from 'elliptic';
|
||||
import { assert, bnToU8a } from '@polkadot/util';
|
||||
|
||||
const EC = elliptic.ec;
|
||||
const ec = new EC('secp256k1');
|
||||
@@ -19,7 +20,7 @@ export default function secp256k1KeypairFromSeed (seed: Uint8Array): Keypair {
|
||||
const key = ec.keyFromPrivate(seed);
|
||||
|
||||
return {
|
||||
publicKey: new Uint8Array(key.getPublic().encodeCompressed('array')),
|
||||
secretKey: hexToU8a(`0x${key.getPrivate('hex')}`)
|
||||
publicKey: new Uint8Array(key.getPublic().encodeCompressed()),
|
||||
secretKey: bnToU8a(key.getPrivate(), { bitLength: 256, isLe: false })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,35 +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.
|
||||
|
||||
// mnemonic, secret, public, account_id
|
||||
type Test = [string, string, string, string];
|
||||
|
||||
const tests: Test[] = [
|
||||
[
|
||||
'life fee table ahead modify maximum dumb such tobacco boss dry nurse',
|
||||
'0xf2360e871c830d397fe221382b503f07ddd8763df81a94bb2504390a2fb91f59',
|
||||
'0x036b0aa6beab469dd2b748a0ff5ddbe3d13df1e15c9d28a2aa057212994e127bea',
|
||||
'0xae8e8fcacbaeb607bcdf0bbd7e615f2b4ef484ee54f19d68a7393fb6db2dd9cd'
|
||||
],
|
||||
[
|
||||
'tide survey cradle cover column ugly author wait eye state elder blame',
|
||||
'0x5385355a5118ec732b9dbcf1668ba21db38b07cf79082dafa9a7cc4b52e4abb0',
|
||||
'0x03929e4f93cdad265751ad8f6365185d8e937610d19b510400f5867d542d60a313',
|
||||
'0xf80ea815da66c42f870b687e1530770d5a7936ae81a147b009506d85bd6d621c'
|
||||
],
|
||||
[
|
||||
'laugh fish flee cake approve butter april dynamic myth license ticket lobster',
|
||||
'0x83ec65cf9a8a7442d808aef6f8987599f1ba3be880769bb3a20621b13adbd476',
|
||||
'0x0388299e4cfaa33d180a026bd54a46ad98df129a131320a9d2fd6f80e64bc3db39',
|
||||
'0x35036238dd195f4c2169379354bda6cba5746f67bde03ef59a77a4cea80729bc'
|
||||
],
|
||||
[
|
||||
'animal thing fork recipe exotic pilot inquiry pledge obey slab obtain reveal',
|
||||
'0x0fd50580eb5a58b0eee60c77656dffa50094b539262366f1227d3babfd7343e5',
|
||||
'0x036edc954685ad89f0a23b0fb1eb2b9c3a8600eee9091c758426dfb2bc7889a7c3',
|
||||
'0x2a94b10d1f28810dc4628e7e424b2d08bd3d17fb08f9416d112f17e86c8fa77c'
|
||||
]
|
||||
];
|
||||
|
||||
export default tests;
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -3,9 +3,12 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Keypair } from '../types';
|
||||
import { assert, u8aToU8a, u8aConcat } from '@polkadot/util';
|
||||
import { blake2AsU8a } from '../blake2';
|
||||
import { HashType } from './types';
|
||||
|
||||
import elliptic from 'elliptic';
|
||||
import { assert, bnToU8a, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import hasher from './hasher';
|
||||
|
||||
const EC = elliptic.ec;
|
||||
const ec = new EC('secp256k1');
|
||||
@@ -14,15 +17,15 @@ const ec = new EC('secp256k1');
|
||||
* @name secp256k1Sign
|
||||
* @description Returns message signature of `message`, using the supplied pair
|
||||
*/
|
||||
export default function secp256k1Sign (message: Uint8Array | string, { secretKey }: Partial<Keypair>): Uint8Array {
|
||||
export default function secp256k1Sign (message: Uint8Array | string, { secretKey }: Partial<Keypair>, hashType: HashType = 'blake2'): Uint8Array {
|
||||
assert(secretKey?.length === 32, 'Expected valid secp256k1 secretKey, 32-bytes');
|
||||
|
||||
const messageHash = blake2AsU8a(u8aToU8a(message), 256);
|
||||
const key = ec.keyFromPrivate(secretKey);
|
||||
const ecsig = key.sign(messageHash);
|
||||
const rParam = new Uint8Array(ecsig.r.toArray());
|
||||
const sParam = new Uint8Array(ecsig.s.toArray());
|
||||
const recoveryParam = Uint8Array.of(ecsig.recoveryParam || 0);
|
||||
const ecsig = key.sign(hasher(hashType, message));
|
||||
|
||||
return u8aConcat(rParam, sParam, recoveryParam);
|
||||
return u8aConcat(
|
||||
bnToU8a(ecsig.r, { bitLength: 256, isLe: false }),
|
||||
bnToU8a(ecsig.s, { bitLength: 256, isLe: false }),
|
||||
new Uint8Array([ecsig.recoveryParam || 0])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
import { blake2AsU8a } from '../blake2';
|
||||
|
||||
import randomAsU8a from '../random/asU8a';
|
||||
import pairFromSeed from './keypair/fromSeed';
|
||||
import hasher from './hasher';
|
||||
import sign from './sign';
|
||||
import verify from './verify';
|
||||
|
||||
@@ -26,11 +27,53 @@ describe('sign and verify', (): void => {
|
||||
expect(sign(MESSAGE, pair)).toHaveLength(65);
|
||||
});
|
||||
|
||||
it('can sign and verify a message by random key', (): void => {
|
||||
it('signs/verifies a message by random key (blake2)', (): void => {
|
||||
const pair = pairFromSeed(randomAsU8a());
|
||||
const signature = sign(MESSAGE, pair);
|
||||
const address = blake2AsU8a(pair.publicKey, 256);
|
||||
const address = hasher('blake2', pair.publicKey);
|
||||
|
||||
expect(verify(MESSAGE, signature, address)).toBe(true);
|
||||
});
|
||||
|
||||
it('signs/verifies a message by random key (keccak)', (): void => {
|
||||
const pair = pairFromSeed(randomAsU8a());
|
||||
const signature = sign(MESSAGE, pair, 'keccak');
|
||||
const address = hasher('keccak', pair.publicKey);
|
||||
|
||||
expect(verify(MESSAGE, signature, address, 'keccak')).toBe(true);
|
||||
});
|
||||
|
||||
it('fails verification on hasher mismatches', (): void => {
|
||||
const pair = pairFromSeed(randomAsU8a());
|
||||
const signature = sign(MESSAGE, pair, 'keccak');
|
||||
const address = hasher('keccak', pair.publicKey);
|
||||
|
||||
expect(verify(MESSAGE, signature, address, 'blake2')).toBe(false);
|
||||
});
|
||||
|
||||
it('works over a range of random keys (blake2)', (): void => {
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const pair = pairFromSeed(randomAsU8a());
|
||||
|
||||
try {
|
||||
expect(verify(MESSAGE, sign(MESSAGE, pair, 'blake2'), hasher('blake2', pair.publicKey), 'blake2')).toBe(true);
|
||||
} catch (error) {
|
||||
console.error(`blake2 failed on #${i}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}, 120000);
|
||||
|
||||
it('works over a range of random keys (keccak)', (): void => {
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const pair = pairFromSeed(randomAsU8a());
|
||||
|
||||
try {
|
||||
expect(verify(MESSAGE, sign(MESSAGE, pair, 'keccak'), hasher('keccak', pair.publicKey), 'keccak')).toBe(true);
|
||||
} catch (error) {
|
||||
console.error(`keccak failed on #${i}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}, 120000);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export type HashType = 'blake2' | 'keccak';
|
||||
@@ -2,9 +2,12 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { u8aToU8a } from '@polkadot/util';
|
||||
import { blake2AsU8a } from '../blake2';
|
||||
import { HashType } from './types';
|
||||
|
||||
import elliptic from 'elliptic';
|
||||
import { assert, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
import hasher from './hasher';
|
||||
|
||||
const EC = elliptic.ec;
|
||||
const ec = new EC('secp256k1');
|
||||
@@ -13,8 +16,11 @@ 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): boolean {
|
||||
export default function secp256k1Verify (message: Uint8Array | string, signature: Uint8Array | string, address: Uint8Array | string, hashType: HashType = 'blake2'): boolean {
|
||||
const signatureU8a = u8aToU8a(signature);
|
||||
|
||||
assert(signatureU8a.length === 65, `Expected signature with 65 bytes, ${signatureU8a.length} found instead`);
|
||||
|
||||
const sig = {
|
||||
r: signatureU8a.slice(0, 32),
|
||||
s: signatureU8a.slice(32, 64)
|
||||
@@ -22,9 +28,9 @@ export default function secp256k1Verify (message: Uint8Array | string, signature
|
||||
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(blake2AsU8a(message, 256), sig, recovery)
|
||||
.encodeCompressed(null)
|
||||
ec.recoverPubKey(hasher(hashType, message), sig, recovery)
|
||||
.encodeCompressed()
|
||||
);
|
||||
|
||||
return Buffer.compare(blake2AsU8a(publicKey, 256), u8aToU8a(address)) === 0;
|
||||
return Buffer.compare(hasher(hashType, publicKey), u8aToU8a(address)) === 0;
|
||||
}
|
||||
|
||||
@@ -14,13 +14,23 @@ import secp256k1Verify from '../secp256k1/verify';
|
||||
const VERIFIERS: [KeypairType, (message: Uint8Array | string, signature: Uint8Array | string, publicKey: Uint8Array | string) => boolean][] = [
|
||||
['ed25519', naclVerify],
|
||||
['sr25519', schnorrkelVerify],
|
||||
['ecdsa', secp256k1Verify]
|
||||
[
|
||||
'ecdsa',
|
||||
(message: Uint8Array | string, signature: Uint8Array | string, publicKey: Uint8Array | string) =>
|
||||
secp256k1Verify(message, signature, publicKey, 'blake2')
|
||||
],
|
||||
[
|
||||
'ethereum',
|
||||
(message: Uint8Array | string, signature: Uint8Array | string, publicKey: Uint8Array | string) =>
|
||||
secp256k1Verify(message, signature, publicKey, 'keccak')
|
||||
]
|
||||
];
|
||||
|
||||
const CRYPTO_TYPES: KeypairType[] = [
|
||||
'ed25519',
|
||||
'sr25519',
|
||||
'ecdsa'
|
||||
'ecdsa',
|
||||
'ethereum'
|
||||
];
|
||||
|
||||
function verifyDetect (result: VerifyResult, message: Uint8Array | string, signature: Uint8Array, publicKey: Uint8Array): VerifyResult {
|
||||
@@ -48,8 +58,9 @@ function verifyMultisig (result: VerifyResult, message: Uint8Array | string, sig
|
||||
|
||||
try {
|
||||
result.isValid = {
|
||||
ecdsa: (): boolean => secp256k1Verify(message, signature.subarray(1), publicKey),
|
||||
ecdsa: (): boolean => secp256k1Verify(message, signature.subarray(1), publicKey, 'blake2'),
|
||||
ed25519: (): boolean => naclVerify(message, signature.subarray(1), publicKey),
|
||||
ethereum: (): boolean => secp256k1Verify(message, signature.subarray(1), publicKey, 'keccak'),
|
||||
none: (): boolean => { throw Error('no verify for `none` crypto type'); },
|
||||
sr25519: (): boolean => schnorrkelVerify(message, signature.subarray(1), publicKey)
|
||||
}[result.crypto]();
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface Seedpair {
|
||||
seed: Uint8Array;
|
||||
}
|
||||
|
||||
export type KeypairType = 'ed25519' | 'sr25519' | 'ecdsa';
|
||||
export type KeypairType = 'ed25519' | 'sr25519' | 'ecdsa' | 'ethereum';
|
||||
|
||||
export interface VerifyResult {
|
||||
crypto: 'none' | KeypairType;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@polkadot/util",
|
||||
"version": "3.1.1",
|
||||
"version": "3.2.1",
|
||||
"description": "A collection of useful utilities for @polkadot",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
|
||||
Reference in New Issue
Block a user