Skip to content

Commit

Permalink
Introduce key suri (path derivation) (#358)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jacogr authored Mar 13, 2019
1 parent a1e56cf commit f1ca4ee
Show file tree
Hide file tree
Showing 37 changed files with 564 additions and 120 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"packages": [
"packages/*"
],
"version": "0.35.10"
"version": "0.36.0"
}
4 changes: 2 additions & 2 deletions packages/chainspec/package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down Expand Up @@ -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"
}
}
6 changes: 3 additions & 3 deletions packages/db/package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions packages/keyring/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@polkadot/keyring",
"version": "0.35.10",
"version": "0.36.0",
"description": "Keyring management",
"main": "index.js",
"engines": {
Expand Down Expand Up @@ -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"
}
Expand Down
64 changes: 53 additions & 11 deletions packages/keyring/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
}
Expand All @@ -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)
*/
Expand All @@ -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;
}

Expand All @@ -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));
}

Expand All @@ -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);
}

Expand All @@ -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
Expand Down
23 changes: 13 additions & 10 deletions packages/keyring/src/pair/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions packages/keyring/src/pair/nobody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
5 changes: 3 additions & 2 deletions packages/keyring/src/pair/toJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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),
Expand Down
75 changes: 75 additions & 0 deletions packages/keyring/src/suri.spec.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
Loading

0 comments on commit f1ca4ee

Please sign in to comment.