Introduce key suri (path derivation) (#358)

* Introduce key suri (path derivation)

* Errant console.log

* let -> const

* Add seed derivation from path

* Allow new-generation derivation

* Add addFromUri to keyring

* Use addFromUri in testing keyring

* Add suri tests

* Prepare for sr25519 derivation

* CHANGELOG entry

* ed25519 is aligned with substrate

* Bump version
This commit is contained in:
Jaco Greeff
2019-03-13 14:39:10 +01:00
committed by GitHub
parent a1e56cfb81
commit f1ca4eef0e
37 changed files with 564 additions and 120 deletions
+4
View File
@@ -1,3 +1,7 @@
# 0.36.1
- Add `fromUri` to keyring along with hard & soft key derivation.
# 0.35.1
- Remove NodeJs-only `syncify` function, not exported by the package (with binary dependency for Node)
+1 -1
View File
@@ -9,5 +9,5 @@
"packages": [
"packages/*"
],
"version": "0.35.10"
"version": "0.36.0"
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/chainspec",
"version": "0.35.10",
"version": "0.36.0",
"description": "Contains chain specifications for known chains",
"main": "index.js",
"keywords": [
@@ -29,6 +29,6 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/chainspec#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/util": "^0.35.10"
"@polkadot/util": "^0.36.0"
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/db",
"version": "0.35.10",
"version": "0.36.0",
"description": "Implementation of a basic sync in-memory and on-disk databases with overlays",
"main": "index.js",
"keywords": [
@@ -29,8 +29,8 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/db#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/trie-hash": "^0.35.10",
"@polkadot/util": "^0.35.10",
"@polkadot/trie-hash": "^0.36.0",
"@polkadot/util": "^0.36.0",
"@types/mkdirp": "^0.5.2",
"@types/rimraf": "^2.0.2",
"@types/snappy": "^6.0.0",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/keyring",
"version": "0.35.10",
"version": "0.36.0",
"description": "Keyring management",
"main": "index.js",
"engines": {
@@ -31,8 +31,8 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/keyring#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/util": "^0.35.10",
"@polkadot/util-crypto": "^0.35.10",
"@polkadot/util": "^0.36.0",
"@polkadot/util-crypto": "^0.36.0",
"@types/bs58": "^4.0.0",
"bs58": "^4.0.1"
}
+53 -11
View File
@@ -2,10 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringInstance, KeyringPair, KeyringPair$Json, KeyringPair$Meta, KeyringOptions, KeyringPairType } from './types';
import { KeypairType } from '@polkadot/util-crypto/types';
import { KeyringInstance, KeyringPair, KeyringPair$Json, KeyringPair$Meta, KeyringOptions } from './types';
import { assert, hexToU8a, isNumber } from '@polkadot/util/index';
import { mnemonicToSeed , naclKeypairFromSeed as naclFromSeed, schnorrkelKeypairFromSeed as schnorrkelFromSeed } from '@polkadot/util-crypto/index';
import { assert, hexToU8a, isNumber, isHex, stringToU8a } from '@polkadot/util/index';
import { keyExtract, mnemonicToSeed , naclKeypairFromSeed as naclFromSeed, schnorrkelKeypairFromSeed as schnorrkelFromSeed, mnemonicToMiniSecret, keyFromPath } from '@polkadot/util-crypto/index';
import { decodeAddress, encodeAddress, setAddressPrefix } from './address';
import createPair from './pair';
@@ -29,13 +30,15 @@ import Pairs from './pairs';
*/
export default class Keyring implements KeyringInstance {
private _pairs: Pairs;
private _type: KeyringPairType;
private _type: KeypairType;
constructor (options: KeyringOptions = {}) {
options.type = options.type || 'ed25519';
constructor (options: KeyringOptions = { type: 'ed25519' }) {
assert(options && ['ed25519', 'sr25519'].includes(options.type || 'undefined'), `Expected a keyring type of either 'ed25519' or 'sr25519', found '${options.type}`);
this._pairs = new Pairs();
this._type = options.type as KeyringPairType;
this._type = options.type;
setAddressPrefix(isNumber(options.addressPrefix) ? options.addressPrefix : 42);
}
@@ -44,6 +47,20 @@ export default class Keyring implements KeyringInstance {
encodeAddress = encodeAddress;
setAddressPrefix = setAddressPrefix;
/**
* @description True for Ed25519 keyring
*/
get isEd25519 (): boolean {
return this.type === 'ed25519';
}
/**
* @description True for Ed25519 keyring
*/
get isSr25519 (): boolean {
return this.type === 'sr25519';
}
/**
* @description retrieve the pairs (alias for getPairs)
*/
@@ -61,7 +78,7 @@ export default class Keyring implements KeyringInstance {
/**
* @description Returns the type of the keyring, either ed25519 of sr25519
*/
get type (): KeyringPairType {
get type (): KeypairType {
return this._type;
}
@@ -81,7 +98,7 @@ export default class Keyring implements KeyringInstance {
* of an account backup), and then generates a keyring pair from them that it passes to
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
*/
addFromAddress (address: string | Uint8Array, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, type: KeyringPairType = this.type): KeyringPair {
addFromAddress (address: string | Uint8Array, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, type: KeypairType = this.type): KeyringPair {
return this.addPair(createPair(type, { publicKey: this.decodeAddress(address) }, meta, encoded));
}
@@ -107,7 +124,7 @@ export default class Keyring implements KeyringInstance {
* of an account backup), and then generates a keyring pair from it that it passes to
* `addPair` to stores in a keyring pair dictionary the public key of the generated pair as a key and the pair as the associated value.
*/
addFromMnemonic (mnemonic: string, meta: KeyringPair$Meta = {}, type: KeyringPairType = this.type): KeyringPair {
addFromMnemonic (mnemonic: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
return this.addFromSeed(mnemonicToSeed(mnemonic), meta, type);
}
@@ -118,14 +135,39 @@ export default class Keyring implements KeyringInstance {
* Allows user to provide the account seed as an argument, and then generates a keyring pair from it that it passes to
* `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.
*/
addFromSeed (seed: Uint8Array, meta: KeyringPair$Meta = {}, type: KeyringPairType = this.type): KeyringPair {
const keypair = type === 'sr25519'
addFromSeed (seed: Uint8Array, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
const keypair = this.isSr25519
? schnorrkelFromSeed(seed)
: naclFromSeed(seed);
return this.addPair(createPair(type, { ...keypair, seed }, meta, null));
}
/**
* @name addFromUri
* @summary Creates an account via an suri
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed). The secret can be a hex string, mnemonic phrase or a string (to be padded)
*/
addFromUri (suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
const { password, phrase, path } = keyExtract(suri);
let seed;
if (isHex(phrase, 256)) {
seed = hexToU8a(phrase);
} else {
const str = phrase as string;
const parts = str.split(' ');
if ([12, 15, 18, 21, 24].includes(parts.length)) {
seed = mnemonicToMiniSecret(phrase, password);
} else {
seed = stringToU8a(str.padEnd(32));
}
}
return this.addFromSeed(keyFromPath(seed, path, type), meta, type);
}
/**
* @name getPair
* @summary Retrieves an account keyring pair from the Keyring Pair Dictionary, given an account address
+13 -10
View File
@@ -2,8 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeypairType } from '@polkadot/util-crypto/types';
import { KeyringPair, KeyringPair$Json, KeyringPair$Meta, KeyringPairType } from '../types';
import { Keypair, KeypairType } from '@polkadot/util-crypto/types';
import { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '../types';
import { PairInfo } from './types';
import { naclKeypairFromSeed as naclFromSeed, naclSign, naclVerify, schnorrkelKeypairFromSeed as schnorrkelFromSeed, schnorrkelSign, schnorrkelVerify } from '@polkadot/util-crypto/index';
@@ -13,18 +13,21 @@ import decode from './decode';
import encode from './encode';
import toJson from './toJson';
const fromSeed = (type: KeyringPairType, seed: Uint8Array) =>
type === 'sr25519'
const isSr25519 = (type: KeypairType): boolean =>
type === 'sr25519';
const fromSeed = (type: KeypairType, seed: Uint8Array) =>
isSr25519(type)
? schnorrkelFromSeed(seed)
: naclFromSeed(seed);
const sign = (type: KeyringPairType, message: Uint8Array, pair: Partial<KeypairType>) =>
type === 'sr25519'
const sign = (type: KeypairType, message: Uint8Array, pair: Partial<Keypair>) =>
isSr25519(type)
? schnorrkelSign(message, pair)
: naclSign(message, pair);
const verify = (type: KeyringPairType, message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) =>
type === 'sr25519'
const verify = (type: KeypairType, message: Uint8Array, signature: Uint8Array, publicKey: Uint8Array) =>
isSr25519(type)
? schnorrkelVerify(message, signature, publicKey)
: naclVerify(message, signature, publicKey);
@@ -59,7 +62,7 @@ const verify = (type: KeyringPairType, message: Uint8Array, signature: Uint8Arra
* an `encoded` property that is assigned with the encoded public key in hex format, and an `encoding`
* property that indicates whether the public key value of the `encoded` property is encoded or not.
*/
export default function createPair (type: KeyringPairType, { publicKey, seed }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null): KeyringPair {
export default function createPair (type: KeypairType, { publicKey, seed }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null): KeyringPair {
let secretKey: Uint8Array | undefined;
if (seed) {
@@ -98,7 +101,7 @@ export default function createPair (type: KeyringPairType, { publicKey, seed }:
sign(type, message, { publicKey, secretKey }),
toJson: (passphrase?: string): KeyringPair$Json =>
toJson(type, { meta, publicKey }, encode({ publicKey, seed }, passphrase), !!passphrase),
toType: (type: KeyringPairType): KeyringPair =>
toType: (type: KeypairType): KeyringPair =>
createPair(type, { publicKey, seed }, meta, null),
verify: (message: Uint8Array, signature: Uint8Array): boolean =>
verify(type, message, signature, publicKey)
+1
View File
@@ -9,6 +9,7 @@ import addressEncode from '../address/encode';
const publicKey = new Uint8Array(32);
const address = addressEncode(publicKey);
const meta = {
isTesting: true,
name: 'nobody'
};
const json: KeyringPair$Json = {
+3 -2
View File
@@ -2,7 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringPair$Json, KeyringPair$Meta, KeyringPairType } from '../types';
import { KeypairType } from '@polkadot/util-crypto/types';
import { KeyringPair$Json, KeyringPair$Meta } from '../types';
import { u8aToHex } from '@polkadot/util/index';
@@ -12,7 +13,7 @@ type PairStateJson = KeyringPair$Meta & {
publicKey: Uint8Array
};
export default function toJson (type: KeyringPairType, { publicKey, meta }: PairStateJson, encoded: Uint8Array, isEncrypted: boolean): KeyringPair$Json {
export default function toJson (type: KeypairType, { publicKey, meta }: PairStateJson, encoded: Uint8Array, isEncrypted: boolean): KeyringPair$Json {
return {
address: encodeAddress(publicKey),
encoded: u8aToHex(encoded),
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2017-2019 @polkadot/keyring authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
// From https://github.com/paritytech/substrate/wiki/Secret-URI-Test-Vectors
import { u8aToHex } from '@polkadot/util/index';
import { cryptoWaitReady } from '@polkadot/util-crypto/index';
import Keyring from './index';
const PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
const TESTS = [
{
pk: '0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a',
ss: '5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqAS7',
uri: PHRASE
},
{
pk: '0xb69355deefa7a8f33e9297f5af22e680f03597a99d4f4b1c44be47e7a2275802',
ss: '5GC6LfpV352HtJPySfAecb5JdePtf4R9Vq49NUU8RhzgBq1z',
uri: `${PHRASE}///password`
},
{
pk: '0x40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a',
ss: '5DXZzrDxHbkQov4QBAY4TjpwnHCMrKXkomTnKSw8UArBESDT',
uri: `${PHRASE}/foo`
},
{
pk: '0x545ecfd16d2ccbece3c3d8e284ba21b624d53a010d19673285a02aca92a62724',
ss: '5DyL3XpLxWKJgeUYkFFhepm9DWFMHjvW6JKyVst6F6bX1DQw',
uri: `${PHRASE}//foo`
},
{
pk: '0xc6755aae1c6a172dc06a8bcf46d119a56ec04b22d7c673255dfc6fb5d7afd250',
ss: '5GYvCYSJxHTNRMqtkLX5AGpTYjSiaHeCdVgBQJWp6Jb1eWZR',
uri: `${PHRASE}//foo/bar`
},
{
pk: '0xbed7420abfb5398eaa5c1e5ce484bf200c735ee0606ed97048dae9a6e7b1aa59',
ss: '5GNvuAdKoC5mHzmSSctsZAC4WkrhZC5W7FBB4G3N5XMCBhMD',
uri: `${PHRASE}/foo//bar`
},
{
pk: '0xd0c0cb1e54a54222058552d7bf94a8abaaafd778adae57a30a9bba5afb01f17a',
ss: '5GnR66wpdL4hnArhDRyyBCqchgVYXVMQDDSZQWegfSSnAEFh',
uri: `${PHRASE}//foo/bar//42/69`
},
{
pk: '0x12dccbb4d851a7bc36bb4f5f6f84a1caaf04c542509b1e3b84f1f47445c1bf08',
ss: '5CVSJt82L8jYodrwYKCoP6LtC6V2VYPHYMHtHzjhdNr1KDdu',
uri: `${PHRASE}//foo/bar//42/69///password`
}
];
// TODO Enable once we have proper schnorrkel support
describe.skip('keyring.addFromUri', () => {
let keyring: Keyring;
beforeEach(async () => {
keyring = new Keyring({ type: 'sr25519' });
await cryptoWaitReady();
});
TESTS.forEach(({ pk, ss, uri }) => {
it(`creates ${uri}`, () => {
const pair = keyring.addFromUri(uri);
expect(u8aToHex(pair.publicKey())).toEqual(pk);
expect(pair.address()).toEqual(ss);
});
});
});
+15 -37
View File
@@ -4,33 +4,10 @@
import { KeyringInstance, KeyringOptions } from './types';
import { stringToU8a } from '@polkadot/util/index';
import Keyring from './index';
function padSeed (seed: string): Uint8Array {
return stringToU8a(seed.padEnd(32));
}
const SEEDS: { [index: string ]: Uint8Array } = {
alice:
padSeed('Alice'),
bob:
padSeed('Bob'),
charlie:
padSeed('Charlie'),
dave:
padSeed('Dave'),
eve:
padSeed('Eve'),
ferdie:
padSeed('Ferdie')
// NOTE These were originally part of tests, don't remove completely (yet), first check impact
// one:
// padSeed('12345678901234567890123456789012'),
// two:
// hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60')
};
const DEV_PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
const SEEDS = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve', 'Ferdie'];
/**
* @name testKeyring
@@ -38,21 +15,22 @@ const SEEDS: { [index: string ]: Uint8Array } = {
* @description The test accounts (i.e. alice, bob, dave, eve, ferdie)
* are available on the dev chain and each test account is initialised with DOT funds.
*/
export default function testKeyring (options?: KeyringOptions): KeyringInstance {
export default function testKeyring (options?: KeyringOptions, isHdKd: boolean = false): KeyringInstance {
const keyring = new Keyring(options);
Object
.keys(SEEDS)
.forEach((name) => {
const pair = keyring.addFromSeed(SEEDS[name], {
isTesting: true,
name
});
pair.lock = () => {
// we don't have lock/unlock functionality here
};
SEEDS.forEach((entry) => {
const phrase = isHdKd
? `${DEV_PHRASE}//${entry}`
: entry;
const pair = keyring.addFromUri(phrase, {
isTesting: true,
name: entry.toLowerCase()
});
pair.lock = () => {
// we don't have lock/unlock functionality here
};
});
return keyring;
}
+18 -2
View File
@@ -2,10 +2,26 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import testingPairs from './testingPairs';
describe('testing', () => {
it('creates without failing', () => {
expect(
require('./testingPairs')
).toBeDefined();
Object.keys(testingPairs())
).toHaveLength(1 + 6);
});
describe('ed25519', () => {
it('has the correct address for Alice (non-HDKD)', () => {
expect(
testingPairs({ type: 'ed25519' }, false).alice.address()
).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ');
});
it('has the correct address for Alice (HDKD)', () => {
expect(
testingPairs({ type: 'ed25519' }, true).alice.address()
).toEqual('5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TmTd');
});
});
});
+3 -4
View File
@@ -11,16 +11,15 @@ type TestKeyringMap = {
[index: string]: KeyringPair
};
export default function testKeyringPairs (options?: KeyringOptions): TestKeyringMap {
const keyring = createKeyring(options);
export default function testKeyringPairs (options?: KeyringOptions, isHdKd: boolean = false): TestKeyringMap {
const keyring = createKeyring(options, isHdKd);
const pairs = keyring.getPairs();
return pairs.reduce((result, pair) => {
const { name } = pair.getMeta();
// @ts-ignore dunno what the complaint here is about...
result[name as string] = pair;
return result;
}, { 'nobody': nobody() });
}, { 'nobody': nobody() } as TestKeyringMap);
}
+12 -10
View File
@@ -2,13 +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 { KeypairType } from '@polkadot/util-crypto/types';
import { Prefix } from './address/types';
export type KeyringPairType = 'ed25519' | 'sr25519';
export type KeyringOptions = {
addressPrefix?: Prefix,
type?: KeyringPairType
type?: KeypairType
};
export type KeyringPair$Meta = {
@@ -18,7 +17,7 @@ export type KeyringPair$Meta = {
export type KeyringPair$JsonVersion = '0' | '1';
export type KeyringPair$JsonEncoding = {
content: ['pkcs8', KeyringPairType],
content: ['pkcs8', KeypairType],
type: 'xsalsa20-poly1305' | 'none',
version: KeyringPair$JsonVersion
};
@@ -31,7 +30,7 @@ export type KeyringPair$Json = {
};
export interface KeyringPair {
readonly type: KeyringPairType;
readonly type: KeypairType;
address: () => string;
decodePkcs8: (passphrase?: string, encoded?: Uint8Array) => void;
@@ -43,7 +42,7 @@ export interface KeyringPair {
setMeta: (meta: KeyringPair$Meta) => void;
sign (message: Uint8Array): Uint8Array;
toJson (passphrase?: string): KeyringPair$Json;
toType (type: KeyringPairType): KeyringPair;
toType (type: KeypairType): KeyringPair;
verify (message: Uint8Array, signature: Uint8Array): boolean;
}
@@ -55,19 +54,22 @@ export interface KeyringPairs {
}
export interface KeyringInstance {
readonly isEd25519: boolean;
readonly isSr25519: boolean;
readonly pairs: Array<KeyringPair>;
readonly publicKeys: Array<Uint8Array>;
readonly type: KeyringPairType;
readonly type: KeypairType;
decodeAddress (encoded: string | Uint8Array): Uint8Array;
encodeAddress (key: Uint8Array | string): string;
setAddressPrefix (prefix: Prefix): void;
addPair (pair: KeyringPair): KeyringPair;
addFromAddress (address: string | Uint8Array, meta?: KeyringPair$Meta, encoded?: Uint8Array | null, type?: KeyringPairType): KeyringPair;
addFromMnemonic (mnemonic: string, meta?: KeyringPair$Meta, type?: KeyringPairType): KeyringPair;
addFromSeed (seed: Uint8Array, meta?: KeyringPair$Meta, type?: KeyringPairType): KeyringPair;
addFromAddress (address: string | Uint8Array, meta?: KeyringPair$Meta, encoded?: Uint8Array | null, type?: KeypairType): KeyringPair;
addFromJson (pair: KeyringPair$Json): KeyringPair;
addFromMnemonic (mnemonic: string, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
addFromSeed (seed: Uint8Array, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
addFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair;
getPair (address: string | Uint8Array): KeyringPair;
getPairs (): Array<KeyringPair>;
getPublicKeys (): Array<Uint8Array>;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/trie-codec",
"version": "0.35.10",
"version": "0.36.0",
"description": "This is an implementation of the codec for Substrate Trie",
"main": "index.js",
"keywords": [
@@ -29,6 +29,6 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/trie-codec#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/util": "^0.35.10"
"@polkadot/util": "^0.36.0"
}
}
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/trie-db",
"version": "0.35.10",
"version": "0.36.0",
"description": "This is an implementation of the modified merkle patricia tree as specified in Ethereum's yellow paper, adapted for Polkadot",
"main": "index.js",
"keywords": [
@@ -29,15 +29,15 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/trie-db#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/db": "^0.35.10",
"@polkadot/trie-codec": "^0.35.10",
"@polkadot/trie-hash": "^0.35.10",
"@polkadot/util": "^0.35.10",
"@polkadot/util-crypto": "^0.35.10"
"@polkadot/db": "^0.36.0",
"@polkadot/trie-codec": "^0.36.0",
"@polkadot/trie-hash": "^0.36.0",
"@polkadot/util": "^0.36.0",
"@polkadot/util-crypto": "^0.36.0"
},
"devDependencies": {
"@polkadot/chainspec": "^0.35.10",
"@polkadot/util-rlp": "^0.35.10",
"@polkadot/chainspec": "^0.36.0",
"@polkadot/util-rlp": "^0.36.0",
"ethereumjs-testing": "1.0.4"
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/trie-hash",
"version": "0.35.10",
"version": "0.36.0",
"description": "Generates trie roots from key-value pairs",
"main": "index.js",
"keywords": [
@@ -29,9 +29,9 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/trie-hash#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/util": "^0.35.10"
"@polkadot/util": "^0.36.0"
},
"devDependencies": {
"@polkadot/chainspec": "^0.35.10"
"@polkadot/chainspec": "^0.36.0"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/util-crypto",
"version": "0.35.10",
"version": "0.36.0",
"description": "A collection of useful crypto utilities for @polkadot",
"main": "index.js",
"keywords": [
@@ -30,7 +30,7 @@
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/schnorrkel-js": "^0.1.2-3",
"@polkadot/util": "^0.35.10",
"@polkadot/util": "^0.36.0",
"@types/bip39": "^2.4.2",
"@types/pbkdf2": "^3.0.0",
"@types/secp256k1": "^3.5.0",
+1
View File
@@ -6,6 +6,7 @@ import { schnorrkelWaitReady } from './schnorrkel';
export * from './blake2';
export * from './keccak';
export * from './key';
export * from './mnemonic';
export * from './nacl';
export * from './random';
@@ -0,0 +1,85 @@
// Copyright 2017-2019 @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 BN from 'bn.js';
import { bnToHex, compactAddLength, hexToU8a, isBn, isHex, isNumber, isString, stringToU8a } from '@polkadot/util/index';
import blake2AsU8a from '../blake2/asU8a';
const RE_NUMBER = /^\d+$/;
const JUNCTION_ID_LEN: number = 32;
const BN_OPTIONS = {
bitLength: 256,
isLe: true
};
export default class DeriveJunction {
private _chainCode: Uint8Array = new Uint8Array(32);
private _isHard: boolean = false;
static from (value: string): DeriveJunction {
const [code, isHard] = value[0] === '/'
? [value.substr(1), true]
: [value, false];
const result = new DeriveJunction();
result.soft(
RE_NUMBER.test(code)
? parseInt(code, 10)
: code
);
return isHard
? result.harden()
: result;
}
get chainCode (): Uint8Array {
return this._chainCode;
}
get isHard (): boolean {
return this._isHard;
}
get isSoft (): boolean {
return !this._isHard;
}
hard (value: number | BN | string | Uint8Array): DeriveJunction {
return this.soft(value).harden();
}
harden (): DeriveJunction {
this._isHard = true;
return this;
}
soft (value: number | BN | string | Uint8Array): DeriveJunction {
if (isNumber(value) || isBn(value)) {
return this.soft(bnToHex(value, BN_OPTIONS));
} else if (isString(value)) {
return isHex(value)
? this.soft(hexToU8a(value))
: this.soft(compactAddLength(stringToU8a(value)));
}
if (value.length > JUNCTION_ID_LEN) {
return this.soft(blake2AsU8a(value));
}
this._chainCode.fill(0);
this._chainCode.set(value, 0);
return this;
}
soften (): DeriveJunction {
this._isHard = false;
return this;
}
}
@@ -0,0 +1,131 @@
// Copyright 2017-2019 @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 keyExtract from './extract';
describe('keyExtract', () => {
it('derives on "hello world"', () => {
expect(
keyExtract('hello world')
).toEqual({
path: [],
phrase: 'hello world'
});
});
it('derives on "hello world/1', () => {
expect(
keyExtract('hello world/1')
).toEqual({
path: [
{ _isHard: false, _chainCode: Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'hello world'
});
});
it('derives on "hello world/DOT', () => {
expect(
keyExtract('hello world/DOT')
).toEqual({
path: [
{ _isHard: false, _chainCode: Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'hello world'
});
});
it('derives on "hello world//1', () => {
expect(
keyExtract('hello world//1')
).toEqual({
path: [
{ _isHard: true, _chainCode: Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'hello world'
});
});
it('derives on "hello world//DOT', () => {
expect(
keyExtract('hello world//DOT')
).toEqual({
path: [
{ _isHard: true, _chainCode: Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'hello world'
});
});
it('derives on "hello world//1/DOT', () => {
expect(
keyExtract('hello world//1/DOT')
).toEqual({
path: [
{ _isHard: true, _chainCode: Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) },
{ _isHard: false, _chainCode: Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'hello world'
});
});
it('derives on "hello world//DOT/1', () => {
expect(
keyExtract('hello world//DOT/1')
).toEqual({
path: [
{ _isHard: true, _chainCode: Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) },
{ _isHard: false, _chainCode: Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'hello world'
});
});
it('derives on "hello world///password"', () => {
expect(
keyExtract('hello world///password')
).toEqual({
password: 'password',
path: [],
phrase: 'hello world'
});
});
it('derives on "hello world//1/DOT///password"', () => {
expect(
keyExtract('hello world//1/DOT///password')
).toEqual({
path: [
{ _isHard: true, _chainCode: Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) },
{ _isHard: false, _chainCode: Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
password: 'password',
phrase: 'hello world'
});
});
it('derives on "hello world/1//DOT///password"', () => {
expect(
keyExtract('hello world/1//DOT///password')
).toEqual({
path: [
{ _isHard: false, _chainCode: Uint8Array.from([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) },
{ _isHard: true, _chainCode: Uint8Array.from([12, 68, 79, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
password: 'password',
phrase: 'hello world'
});
});
it('derives on actual Alice', () => {
expect(
keyExtract('bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice')
).toEqual({
path: [
{ _isHard: true, _chainCode: Uint8Array.from([20, 65, 108, 105, 99, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) }
],
phrase: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk'
});
});
});
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2017-2019 @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, isNull } from '@polkadot/util/index';
import DeriveJunction from './DeriveJunction';
type DeriveResult = {
password?: string,
path: Array<DeriveJunction>,
phrase: string
};
const RE_CAPTURE = /^(\w+( \w+)*)((\/\/?[^\/]+)*)(\/\/\/(.*))?$/;
const RE_JUNCTION = /\/(\/?)([^\/]+)/g;
/**
* @description Extracts the phrase, path and password from a SURI format for specifying secret keys `<secret>/<soft-key>//<hard-key>///<password>` (the `///password` may be omitted, and `/<soft-key>` and `//<hard-key>` maybe repeated and mixed).
*/
export default function keyExtract (suri: string): DeriveResult {
const matches = suri.match(RE_CAPTURE);
assert(!isNull(matches), `Unable to match '${suri}' to a secret URI`);
const [, phrase, , junctions, , , password] = matches as Array<any>;
const parts = junctions.match(RE_JUNCTION);
const path: Array<DeriveJunction> = [];
if (parts) {
parts.forEach((value: string) => {
path.push(DeriveJunction.from(value.substr(1)));
});
}
return {
password,
path,
phrase
};
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @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 { KeypairType } from '../types';
import DeriveJunction from './DeriveJunction';
import keyHdkdEd15519 from './hdkdEd25519';
import keyHdkdSr15519 from './hdkdSr25519';
export default function keyFromPath (seed: Uint8Array, path: Array<DeriveJunction>, type: KeypairType): Uint8Array {
return path.reduce((seed, junction) => {
return type === 'ed25519'
? keyHdkdEd15519(seed, junction)
: keyHdkdSr15519(seed, junction);
}, seed);
}
@@ -0,0 +1,18 @@
// Copyright 2017-2019 @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, compactAddLength, stringToU8a, u8aConcat } from '@polkadot/util/index';
import blake2AsU8a from '../blake2/asU8a';
import DeriveJunction from './DeriveJunction';
const HDKD = compactAddLength(stringToU8a('Ed25519HDKD'));
export default function keyHdkdEd25519 (seed: Uint8Array, { chainCode, isHard }: DeriveJunction): Uint8Array {
assert(isHard, 'A soft key was found in the path (and is unsupported)');
return blake2AsU8a(
u8aConcat(HDKD, seed, chainCode)
);
}
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @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 { stringToU8a, u8aConcat } from '@polkadot/util/index';
import blake2AsU8a from '../blake2/asU8a';
import DeriveJunction from './DeriveJunction';
const HDKD = stringToU8a('SchnorrRistrettoHDKD');
// FIXME This should pull in from schnorrkel and do the magic there
export default function keyHdkdSr25519 (seed: Uint8Array, { chainCode }: DeriveJunction): Uint8Array {
return blake2AsU8a(
u8aConcat(HDKD, seed, chainCode)
);
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2017-2019 @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 Create keys from paths, seeds and password
*/
export { default as keyExtract } from './extract';
export { default as keyFromPath } from './fromPath';
export { default as keyHdkdEd25519 } from './hdkdEd25519';
export { default as keyHdkdSr25519 } from './hdkdEd25519';
@@ -2,12 +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 { KeypairType } from '../../types';
import { Keypair } from '../../types';
import { naclKeypairFromRandom } from '../index';
describe('naclKeypairFromRandom', () => {
let keypair: KeypairType;
let keypair: Keypair;
beforeEach(() => {
keypair = naclKeypairFromRandom();
@@ -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 { KeypairType } from '../../types';
import { Keypair } from '../../types';
import nacl from 'tweetnacl';
@@ -20,6 +20,6 @@ import nacl from 'tweetnacl';
* naclKeypairFromRandom(); // => { secretKey: [...], publicKey: [...] }
* ```
*/
export default function naclKeypairFromRandom (): KeypairType {
export default function naclKeypairFromRandom (): Keypair {
return nacl.sign.keyPair();
}
@@ -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 { KeypairType } from '../../types';
import { Keypair } from '../../types';
import nacl from 'tweetnacl';
@@ -20,6 +20,6 @@ import nacl from 'tweetnacl';
* naclKeypairFromSecret(...); // => { secretKey: [...], publicKey: [...] }
* ```
*/
export default function naclKeypairFromSecret (secret: Uint8Array): KeypairType {
export default function naclKeypairFromSecret (secret: Uint8Array): Keypair {
return nacl.sign.keyPair.fromSecretKey(secret);
}
@@ -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 { KeypairType } from '../../types';
import { Keypair } from '../../types';
import nacl from 'tweetnacl';
@@ -20,6 +20,6 @@ import nacl from 'tweetnacl';
* naclKeypairFromSeed(...); // => { secretKey: [...], publicKey: [...] }
* ```
*/
export default function naclKeypairFromSeed (seed: Uint8Array): KeypairType {
export default function naclKeypairFromSeed (seed: Uint8Array): Keypair {
return nacl.sign.keyPair.fromSeed(seed);
}
@@ -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 { KeypairType } from '../../types';
import { Keypair } from '../../types';
import { stringToU8a } from '@polkadot/util/index';
@@ -23,7 +23,7 @@ import fromSeed from './fromSeed';
* naclKeypairFromString('test'); // => { secretKey: [...], publicKey: [...] }
* ```
*/
export default function naclKeypairFromString (value: string): KeypairType {
export default function naclKeypairFromString (value: string): Keypair {
return fromSeed(
blake2AsU8a(
stringToU8a(value),
+2 -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 { KeypairType } from '../types';
import { Keypair } from '../types';
import nacl from 'tweetnacl';
import { assert } from '@polkadot/util/index';
@@ -21,7 +21,7 @@ import { assert } from '@polkadot/util/index';
* naclSign([...], [...]); // => [...]
* ```
*/
export default function naclSign (message: Uint8Array, { secretKey }: Partial<KeypairType>): Uint8Array {
export default function naclSign (message: Uint8Array, { secretKey }: Partial<Keypair>): Uint8Array {
assert(secretKey && secretKey.length === 64, 'Expected valid secretKey, 64 bytes');
return nacl.sign.detached(message, secretKey as Uint8Array);
@@ -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 { KeypairType } from '../../types';
import { Keypair } from '../../types';
import schnorrkel from '@polkadot/schnorrkel-js';
@@ -13,7 +13,7 @@ const PUB_LEN = 32;
* @name schnorrkelKeypairFromSeed
* @description Returns a object containing a `publicKey` & `secretKey` generated from the supplied seed.
*/
export default function schnorrkelKeypairFromSeed (seed: Uint8Array): KeypairType {
export default function schnorrkelKeypairFromSeed (seed: Uint8Array): Keypair {
const full = schnorrkel.keypair_from_seed(seed);
return {
+2 -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 { KeypairType } from '../types';
import { Keypair } from '../types';
import { assert } from '@polkadot/util/index';
@@ -12,7 +12,7 @@ import schnorrkel from '@polkadot/schnorrkel-js';
* @name schnorrkelSign
* @description Returns message signature of `message`, using the supplied pair
*/
export default function schnorrkelSign (message: Uint8Array, { publicKey, secretKey }: Partial<KeypairType>): Uint8Array {
export default function schnorrkelSign (message: Uint8Array, { publicKey, secretKey }: Partial<Keypair>): Uint8Array {
assert(publicKey && publicKey.length === 32, 'Expected valid publicKey, 32-bytes');
assert(secretKey && secretKey.length === 64, 'Expected valid secretKey, 64-bytes');
+3 -1
View File
@@ -2,7 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export type KeypairType = {
export type Keypair = {
publicKey: Uint8Array;
secretKey: Uint8Array;
};
export type KeypairType = 'ed25519' | 'sr25519';
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/util-rlp",
"version": "0.35.10",
"version": "0.36.0",
"description": "RLP encoding and decoding",
"main": "index.js",
"keywords": [
@@ -31,7 +31,7 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/util-rlp#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/util": "^0.35.10"
"@polkadot/util": "^0.36.0"
},
"devDependencies": {
"ethereumjs-testing": "1.0.4"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@polkadot/util",
"version": "0.35.10",
"version": "0.36.0",
"description": "A collection of useful utilities for @polkadot",
"main": "index.js",
"keywords": [