Compare commits

...
34 Commits
Author SHA1 Message Date
Github Actions 9808f58540 [CI Skip] release/stable 3.2.1
skip-checks: true
2020-08-17 10:23:06 +00:00
Jaco Greeff 7228100835 3.2 (#663) 2020-08-17 12:17:44 +02:00
Github Actions 08c1b413d2 [CI Skip] release/beta 3.2.0-beta.4
skip-checks: true
2020-08-17 09:34:41 +00:00
Jaco Greeff 27ae11860d Add Ethereum-compatible seed from mnemonic (#662)
* Add Ethereum-compatible seed from mnemonic

* Change encode signature to allow publicKeys

* Make CC happier
2020-08-17 11:28:59 +02:00
Github Actions 9cc984e8ae [CI Skip] release/beta 3.2.0-beta.3
skip-checks: true
2020-08-14 10:48:37 +00:00
Jaco Greeff d352782927 Ethereum keypair support (#661)
* Ethereum keypair support

* keypair support from seed

* Signature verification

* Cleanups

* Cleanup encode (no extra toLower)

* u8aToU8a

* Simulate random failure with range

* Cleanup BN conversions
2020-08-14 12:41:56 +02:00
Jaco Greeff 6e4a5281ed Bump deps (#660) 2020-08-12 17:02:26 +02:00
Github Actions d11f6d9307 [CI Skip] release/beta 3.2.0-beta.2
skip-checks: true
2020-08-11 14:49:56 +00:00
Jaco Greeff 61b57687af Ethereum address utils (#659)
* Ethereum address utils

* export in base
2020-08-11 16:46:01 +02:00
Github Actions 3ab1623a40 [CI Skip] release/beta 3.2.0-beta.1
skip-checks: true
2020-08-11 14:25:08 +00:00
Jaco Greeff f5acd60253 Actually expose base32* functions (#658) 2020-08-11 16:20:39 +02:00
Github Actions c4d368d2b5 [CI Skip] release/beta 3.2.0-beta.0
skip-checks: true
2020-08-11 06:59:01 +00:00
Jaco Greeff e487d0a454 - Allow for hasher to secp256k1 sign/verify functions (#657) 2020-08-11 08:54:52 +02:00
Github Actions 99ffcc52ec [CI Skip] release/stable 3.1.1
skip-checks: true
2020-08-10 04:51:43 +00:00
Jaco Greeff 64510af8d3 3.1 (#655) 2020-08-10 06:47:53 +02:00
Github Actions 52b14db475 [CI Skip] release/beta 3.1.0-beta.8
skip-checks: true
2020-08-09 12:45:13 +00:00
Jaco Greeff c5fe5cd8d4 Bump @polkadot/wasm (#654) 2020-08-09 14:41:40 +02:00
Github Actions bdfc614ad5 [CI Skip] release/beta 3.1.0-beta.7
skip-checks: true
2020-08-09 12:29:56 +00:00
Jaco Greeff 0d03eac307 Add deriveAddress (#653) 2020-08-09 14:26:37 +02:00
Github Actions 6c84edcb9d [CI Skip] release/beta 3.1.0-beta.6
skip-checks: true
2020-08-09 11:31:44 +00:00
Jaco Greeff 9d145e72b8 Base32 encoding/decoding (#652)
* Base32 encoding/decoding

* Attribution

* LOOKUP

* Share base32/58 alphabet validation

* Apply suggestions from code review
2020-08-09 13:27:47 +02:00
Github Actions 4529bc37b5 [CI Skip] release/beta 3.1.0-beta.5
skip-checks: true
2020-08-08 07:07:37 +00:00
Jaco Greeff 038ef42fe5 Bump deps (#651) 2020-08-08 09:03:58 +02:00
Github Actions 1871d36a97 [CI Skip] release/beta 3.1.0-beta.4
skip-checks: true
2020-08-06 12:19:34 +00:00
Jaco Greeff 45c2afae07 Bump berry (#650)
* Bump berry

* Rewrite
2020-08-06 14:15:37 +02:00
Github Actions 51edae2f67 [CI Skip] release/beta 3.1.0-beta.3
skip-checks: true
2020-08-05 14:32:25 +00:00
Jaco Greeff 437314e5ea Bump wasm-crypto (#649)
* Bump wasm-crypto

* Rewrite yarn.lock
2020-08-05 16:28:22 +02:00
Jaco Greeff ce6f9d8bc8 Scrypt via wasm-crypto (#647)
* Scrypt via wasm-crypto

* Bump wasm-crypto
2020-08-05 16:07:38 +02:00
Github Actions 0cf65e1671 [CI Skip] release/beta 3.1.0-beta.2
skip-checks: true
2020-07-31 13:52:39 +00:00
Brad Larson a0251ff650 [FIX] Adjust error message (#646)
I ran into a case where the secret value provided had trailing
whitespace, and when the code ran, it logged that secret value as part
of the stack trace.

This change will make it harder for users to accidentally leak key
material.
2020-07-31 15:48:31 +02:00
Github Actions 9696badcd1 [CI Skip] release/beta 3.1.0-beta.1
skip-checks: true
2020-07-31 08:07:03 +00:00
Jaco Greeff 08817d199d Bump deps (#645) 2020-07-31 10:02:57 +02:00
Github Actions df69a02e6f [CI Skip] release/beta 3.1.0-beta.0
skip-checks: true
2020-07-30 14:02:55 +00:00
Jaco Greeff e845132d89 Bump deps (#644) 2020-07-30 15:58:17 +02:00
66 changed files with 3730 additions and 2775 deletions
Vendored Executable
+86
View File
File diff suppressed because one or more lines are too long
-56
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -8,4 +8,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.js
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-rc.js
yarnPath: .yarn/releases/yarn-2.1.1.js
+15
View File
@@ -1,5 +1,20 @@
# 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
- Use `scrypt` from WASM as available (JS fallback in place)
- Add `base32{Encode, Decode}` utilities (IPFS-compatible alphabet)
- Support IPFS compatibility flag in base58 encoding/decoding
- Update to `@polkadot/wasm-crypto` 1.3.1
## 3.0.1 Jul 27, 2020
- Add Katal ss58Format (Thanks to https://github.com/retotrinkler)
+1 -1
View File
@@ -9,5 +9,5 @@
"packages": [
"packages/*"
],
"version": "3.0.1"
"version": "3.2.1"
}
+5 -5
View File
@@ -21,10 +21,10 @@
"test:one": "polkadot-dev-run-test"
},
"devDependencies": {
"@babel/core": "^7.10.5",
"@polkadot/dev": "^0.55.28",
"@polkadot/ts": "^0.3.29",
"@types/jest": "^26.0.7"
"@babel/core": "^7.11.1",
"@polkadot/dev": "^0.55.38",
"@polkadot/ts": "^0.3.38",
"@types/jest": "^26.0.9"
},
"version": "3.0.1"
"version": "3.2.1"
}
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/keyring",
"version": "3.0.1",
"version": "3.2.1",
"description": "Keyring management",
"main": "index.js",
"publishConfig": {
@@ -27,8 +27,8 @@
},
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/keyring#readme",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/util": "3.0.1",
"@polkadot/util-crypto": "3.0.1"
"@babel/runtime": "^7.11.2",
"@polkadot/util": "3.2.1",
"@polkadot/util-crypto": "3.2.1"
}
}
+25
View File
@@ -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;
+15 -20
View File
@@ -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);
}
+10
View File
@@ -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');
});
});
});
+48 -37
View File
@@ -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)
};
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/util-crypto",
"version": "3.0.1",
"version": "3.2.1",
"description": "A collection of useful crypto utilities for @polkadot",
"main": "index.js",
"keywords": [
@@ -25,9 +25,9 @@
},
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/util-crypto#readme",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/util": "3.0.1",
"@polkadot/wasm-crypto": "^1.2.1",
"@babel/runtime": "^7.11.2",
"@polkadot/util": "3.2.1",
"@polkadot/wasm-crypto": "^1.3.1",
"base-x": "^3.0.8",
"bip39": "^3.0.2",
"blakejs": "^1.1.0",
@@ -7,7 +7,7 @@ import { stringToU8a, u8aToHex } from '@polkadot/util';
import testingPairs, { TestKeyringMap } from '../../../keyring/src/testingPairs';
import decode from './decode';
describe('decode', (): void => {
describe('decodeAddress', (): void => {
let keyring: TestKeyringMap;
beforeAll((): void => {
@@ -0,0 +1,25 @@
// 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 { waitReady } from '@polkadot/wasm-crypto';
import derive from './derive';
describe('deriveAddress', (): void => {
beforeEach(async (): Promise<void> => {
await waitReady();
});
it('derives a known path', (): void => {
expect(
derive('5CZtJLXtVzrBJq1fMWfywDa6XuRwXekGdShPR4b8i9GWSbzB', '/joe/polkadot/0')
).toEqual('5GZ4srnepXvdsuNVoxCGyVZd8ScDm4gkGLTKuaGARy9akjTa');
});
it('fails on hard paths', (): void => {
expect(
() => derive('5CZtJLXtVzrBJq1fMWfywDa6XuRwXekGdShPR4b8i9GWSbzB', '//bob')
).toThrow(/Expected suri to contain a combination of non-hard paths/);
});
});
@@ -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 { Prefix } from './types';
import { assert } from '@polkadot/util';
import DeriveJunction from '../key/DeriveJunction';
import { keyExtractPath } from '../key';
import { schnorrkelDerivePublic } from '../schnorrkel';
import decode from './decode';
import encode from './encode';
/**
* @name deriveAddress
* @summary Creates a sr25519 derived address from the supplied and path.
* @description
* Creates a sr25519 derived address based on the input address/publicKey and the uri supplied.
*/
export default function deriveAddress (who: Uint8Array | string, suri: string, ss58Format?: Prefix): string {
const { path } = keyExtractPath(suri);
assert(path.length && !path.some((path) => path.isHard), 'Expected suri to contain a combination of non-hard paths');
return encode(path.reduce((publicKey: Uint8Array, path: DeriveJunction): Uint8Array => {
return schnorrkelDerivePublic(publicKey, path.chainCode);
}, decode(who)), ss58Format);
}
@@ -12,7 +12,7 @@ import keyDerived from './keyDerived';
/**
* @name encodeDerivedAddress
* @summary Creates a derived address.
* @summary Creates a derived address as used in Substrate utility.
* @description
* Creates a Substrate derived address based on the input address/publicKey and the index supplied.
*/
@@ -7,6 +7,7 @@ import checkAddressChecksum from './checkChecksum';
import createKeyMulti from './keyMulti';
import createKeyDerived from './keyDerived';
import decodeAddress from './decode';
import deriveAddress from './derive';
import encodeAddress from './encode';
import encodeDerivedAddress from './encodeDerived';
import encodeMultiAddress from './encodeMulti';
@@ -21,6 +22,7 @@ export {
createKeyDerived,
createKeyMulti,
decodeAddress,
deriveAddress,
encodeAddress,
encodeDerivedAddress,
encodeMultiAddress,
+12
View File
@@ -0,0 +1,12 @@
// 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.
// https://github.com/multiformats/js-multibase/blob/master/src/constants.js
const BASE32_ALPHABET = 'abcdefghijklmnopqrstuvwxyz234567';
const BITS_PER_CHAR = 5;
export {
BASE32_ALPHABET,
BITS_PER_CHAR
};
@@ -0,0 +1,25 @@
// 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 { u8aToString } from '@polkadot/util';
import { base32Decode } from './';
describe('base32Decode', (): void => {
it('decodes a base32', (): void => {
expect(
u8aToString(
base32Decode('irswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb')
)
).toEqual('Decentralize everything!!');
});
it('decodes a base32 (ipfsCompat)', (): void => {
expect(
u8aToString(
base32Decode('birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb', true)
)
).toEqual('Decentralize everything!!');
});
});
+54
View File
@@ -0,0 +1,54 @@
// 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.
// adapted from https://github.com/multiformats/js-multibase/blob/424709195b46ffb1d6f2f69a7707598ebe751e5e/src/rfc4648.js
import { assert } from '@polkadot/util';
import { BASE32_ALPHABET, BITS_PER_CHAR } from './bs32';
import validate from './validate';
// Build the character lookup table:
const LOOKUP = BASE32_ALPHABET
.split('')
.reduce((lookup: Record<string, number>, char: string, index: number): Record<string, number> => {
lookup[char] = index;
return lookup;
}, {});
/**
* @name base32Decode
* @summary Delookup a base32 value.
* @description
* From the provided input, decode the base32 and return the result as an `Uint8Array`.
*/
export default function base32Decode (value: string, ipfsCompat = false): Uint8Array {
if (ipfsCompat) {
assert(value[0] === 'b', "Expected IPFS base32 identifier 'b' at string start");
value = value.substr(1);
}
validate(value);
const out = new Uint8Array((value.length * BITS_PER_CHAR / 8) | 0);
let bits = 0;
let buffer = 0;
let written = 0;
for (let i = 0; i < value.length; ++i) {
buffer = (buffer << BITS_PER_CHAR) | LOOKUP[value[i]];
bits += BITS_PER_CHAR;
if (bits >= 8) {
bits -= 8;
out[written++] = 0xff & (buffer >> bits);
}
}
assert(!(bits >= BITS_PER_CHAR || 0xff & (buffer << (8 - bits))), 'Unexpected end of data');
return out;
}
@@ -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 { base58Decode } from '../base58';
import { base32Encode } from './';
describe('base32Encode', (): void => {
it('encodes to a base32', (): void => {
expect(
base32Encode('Decentralize everything!!')
).toEqual('irswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb');
});
it('encodes a base58 to a base32', (): void => {
expect(
base32Encode(
base58Decode('zb2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV', true),
true
)
).toEqual('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy');
});
});
+42
View File
@@ -0,0 +1,42 @@
// 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.
// adapted from https://github.com/multiformats/js-multibase/blob/424709195b46ffb1d6f2f69a7707598ebe751e5e/src/rfc4648.js
import { u8aToU8a } from '@polkadot/util';
import { BASE32_ALPHABET, BITS_PER_CHAR } from './bs32';
const MASK = (1 << BITS_PER_CHAR) - 1;
/**
* @name base32Encode
* @summary Creates a base32 value.
* @description
* From the provided input, create the base32 and return the result as a string.
*/
export default function base32Encode (value: Uint8Array | string | Buffer | number[], ipfsCompat = false): string {
const u8a = u8aToU8a(value);
let out = '';
let bits = 0;
let buffer = 0;
for (let i = 0; i < u8a.length; ++i) {
buffer = (buffer << 8) | u8a[i];
bits += 8;
while (bits > BITS_PER_CHAR) {
bits -= BITS_PER_CHAR;
out += BASE32_ALPHABET[MASK & (buffer >> bits)];
}
}
if (bits) {
out += BASE32_ALPHABET[MASK & (buffer << (BITS_PER_CHAR - bits))];
}
return ipfsCompat
? `b${out}`
: out;
}
+11
View File
@@ -0,0 +1,11 @@
// 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.
/**
* @summary Encode and decode base32 values
*/
export { default as base32Decode } from './decode';
export { default as base32Encode } from './encode';
export { default as base32Validate } from './validate';
@@ -0,0 +1,16 @@
// 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 { validateChars } from '../base58/validate';
import { BASE32_ALPHABET } from './bs32';
/**
* @name base32Validate
* @summary Validates a base32 value.
* @description
* Validates the the supplied value is valid base32
*/
export default function base32Validate (value: string): true {
return validateChars('base32', BASE32_ALPHABET, value);
}
+8 -2
View File
@@ -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 { bufferToU8a } from '@polkadot/util';
import { assert, bufferToU8a } from '@polkadot/util';
import { bs58 } from './bs58';
import validate from './validate';
@@ -13,7 +13,13 @@ import validate from './validate';
* @description
* From the provided input, decode the base58 and return the result as an `Uint8Array`.
*/
export default function base58Decode (value: string): Uint8Array {
export default function base58Decode (value: string, ipfsCompat = false): Uint8Array {
if (ipfsCompat) {
assert(value[0] === 'z', "Expected IPFS base32 identifier 'z' at string start");
value = value.substr(1);
}
validate(value);
return bufferToU8a(bs58.decode(value));
@@ -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 { base32Decode } from '../base32';
import { base58Encode } from './';
describe('base58Encode', (): void => {
it('encodes a base32 to a base38', (): void => {
expect(
base58Encode(
base32Decode('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true),
true
)
).toEqual('zb2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV');
});
});
+6 -2
View File
@@ -12,8 +12,12 @@ import { bs58 } from './bs58';
* @description
* From the provided input, create the base58 and return the result as a string.
*/
export default function base58Encode (value: Uint8Array | string | Buffer | number[]): string {
return bs58.encode(
export default function base58Encode (value: Uint8Array | string | Buffer | number[], ipfsCompat = false): string {
const out = bs58.encode(
u8aToBuffer(u8aToU8a(value))
);
return ipfsCompat
? `z${out}`
: out;
}
+11 -7
View File
@@ -4,6 +4,16 @@
import { BASE58_ALPHABET } from './bs58';
export function validateChars (type: string, alphabet: string, value: string): true {
for (let i = 0; i < value.length; i++) {
if (!alphabet.includes(value[i])) {
throw new TypeError(`Invalid ${type} character "${value[i]}" (0x${value.charCodeAt(i).toString(16)}) at index ${i}`);
}
}
return true;
}
/**
* @name base58Validate
* @summary Validates a base58 value.
@@ -11,11 +21,5 @@ import { BASE58_ALPHABET } from './bs58';
* Validates the the supplied value is valid base58
*/
export default function base58Validate (value: string): true {
for (let i = 0; i < value.length; i++) {
if (!BASE58_ALPHABET.includes(value[i])) {
throw new TypeError(`Invalid base58 character "${value[i]}" (0x${value.charCodeAt(i).toString(16)}) at index ${i}`);
}
}
return true;
return validateChars('base58', BASE58_ALPHABET, value);
}
@@ -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);
});
});
+2
View File
@@ -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';
+1 -1
View File
@@ -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()
);
}
@@ -8,7 +8,7 @@ describe('keyExtractSuri', (): void => {
it('does not extract from invalid suri', (): void => {
expect(
(): ExtractResult => keyExtractSuri('//2')
).toThrow(/to a secret URI/);
).toThrow('Unable to match provided value to a secret URI');
});
it('derives on "hello world"', (): void => {
+1 -1
View File
@@ -22,7 +22,7 @@ export default function keyExtract (suri: string): ExtractResult {
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const matches = suri.match(RE_CAPTURE);
assert(!isNull(matches), `Unable to match '${suri}' to a secret URI`);
assert(!isNull(matches), 'Unable to match provided value to a secret URI');
const [, phrase, , derivePath, , , password] = matches as string[];
const { path } = keyExtractPath(derivePath);
+2
View File
@@ -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
};
+1 -1
View File
@@ -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,11 @@
// 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 '../polyfill';
import { sr25519DerivePublicSoft } from '@polkadot/wasm-crypto';
export default function deriveSoft (publicKey: Uint8Array, chainCode: Uint8Array): Uint8Array {
return sr25519DerivePublicSoft(publicKey, chainCode);
}
@@ -5,6 +5,7 @@
import '../polyfill';
export { default as schnorrkelDeriveHard } from './deriveHard';
export { default as schnorrkelDerivePublic } from './derivePublic';
export { default as schnorrkelDeriveSoft } from './deriveSoft';
export { default as schnorrkelKeypairFromSeed } from './keypair/fromSeed';
export { default as schnorrkelSign } from './sign';
+7 -4
View File
@@ -4,8 +4,9 @@
import { Params } from './types';
import scrypt from 'scryptsy';
import scryptsy from 'scryptsy';
import { bufferToU8a, u8aToBuffer, u8aToU8a } from '@polkadot/util';
import { isReady, scrypt } from '@polkadot/wasm-crypto';
import randomAsU8a from '../random/asU8a';
import { DEFAULT_PARAMS } from './defaults';
@@ -17,9 +18,11 @@ interface Result {
}
export default function scryptEncode (passphrase?: Uint8Array | string, salt = randomAsU8a(), params = DEFAULT_PARAMS): Result {
const password = bufferToU8a(
scrypt(u8aToBuffer(u8aToU8a(passphrase)), u8aToBuffer(salt), params.N, params.r, params.p, 64)
);
const password = isReady()
? scrypt(u8aToU8a(passphrase), salt, Math.log2(params.N), params.r, params.p)
: bufferToU8a(
scryptsy(u8aToBuffer(u8aToU8a(passphrase)), u8aToBuffer(salt), params.N, params.r, params.p, 64)
);
return { params, password, salt };
}
@@ -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);
}
+12 -9
View File
@@ -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';
+12 -6
View File
@@ -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 -3
View File
@@ -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]();
+1 -1
View File
@@ -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;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/util",
"version": "3.0.1",
"version": "3.2.1",
"description": "A collection of useful utilities for @polkadot",
"main": "index.js",
"keywords": [
@@ -25,7 +25,7 @@
},
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/util#readme",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@babel/runtime": "^7.11.2",
"@types/bn.js": "^4.11.6",
"bn.js": "^5.1.2",
"camelcase": "^5.3.1",
+2650 -2563
View File
File diff suppressed because it is too large Load Diff