Skip to content

Commit

Permalink
Dev keys should use sr25519 derivation (#361)
Browse files Browse the repository at this point in the history
* Dev keys should use sr25510 derivation

* Rework derive

* Derive hard and soft

* Swap default to isDerived

* typo

* Pull in new schnorrkel-js dep, update tests

* Update nobody
  • Loading branch information
jacogr authored Mar 14, 2019
1 parent c88c7d5 commit 6049f09
Show file tree
Hide file tree
Showing 27 changed files with 148 additions and 91 deletions.
2 changes: 1 addition & 1 deletion packages/keyring/src/address/decode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import testingPairs from '../testingPairs';
import decode from './decode';

const keyring = testingPairs({ type: 'ed25519' });
const keyring = testingPairs({ type: 'ed25519' }, false);

describe('decode', () => {
it('decodes an address', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/keyring/src/address/encode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import testingPairs from '../testingPairs';
import encode from './encode';

const keyring = testingPairs({ type: 'ed25519' });
const keyring = testingPairs({ type: 'ed25519' }, false);

describe('encode', () => {
it('encodes an address to a valid value', () => {
Expand Down
32 changes: 5 additions & 27 deletions packages/keyring/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import Keyring from '.';
import setPrefix from './address/setPrefix';

describe('keypair', () => {
beforeEach(async () => {
await cryptoWaitReady();
});

describe('ed25519', () => {
const publicKeyOne = new Uint8Array([47, 140, 97, 41, 216, 22, 207, 81, 195, 116, 188, 127, 8, 195, 230, 62, 209, 86, 207, 120, 174, 251, 74, 101, 80, 217, 123, 135, 153, 121, 119, 238]);
const publicKeyTwo = new Uint8Array([215, 90, 152, 1, 130, 177, 10, 183, 213, 75, 254, 211, 201, 100, 7, 58, 14, 225, 114, 243, 218, 166, 35, 37, 175, 2, 26, 104, 247, 7, 81, 26]);
Expand Down Expand Up @@ -71,18 +75,6 @@ describe('keypair', () => {

expect(pair.verify(MESSAGE, signature)).toBe(true);
});

it('converts, signs and verifies', () => {
const MESSAGE = stringToU8a('this is a message');
const pair = keypair.getPair(publicKeyOne).toType('sr25519');
const signature = pair.sign(MESSAGE);

expect(pair.type).toBe('sr25519');
expect(pair.publicKey()).toEqual(
new Uint8Array([116, 28, 8, 160, 111, 65, 197, 150, 96, 143, 103, 116, 37, 155, 217, 4, 51, 4, 173, 250, 93, 62, 234, 98, 118, 11, 217, 190, 151, 99, 77, 99])
);
expect(pair.verify(MESSAGE, signature)).toBe(true);
});
});

describe('sr25519', () => {
Expand All @@ -92,9 +84,7 @@ describe('keypair', () => {
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
let keypair: Keyring;

beforeEach(async () => {
await cryptoWaitReady();

beforeEach(() => {
keypair = new Keyring({ type: 'sr25519' });

keypair.addFromSeed(seedOne, {});
Expand Down Expand Up @@ -145,17 +135,5 @@ describe('keypair', () => {

expect(pair.verify(MESSAGE, signature)).toBe(true);
});

it('converts, signs and verifies', () => {
const MESSAGE = stringToU8a('this is a message');
const pair = keypair.getPair(publicKeyOne).toType('ed25519');
const signature = pair.sign(MESSAGE);

expect(pair.type).toBe('ed25519');
expect(pair.publicKey()).toEqual(
new Uint8Array([47, 140, 97, 41, 216, 22, 207, 81, 195, 116, 188, 127, 8, 195, 230, 62, 209, 86, 207, 120, 174, 251, 74, 101, 80, 217, 123, 135, 153, 121, 119, 238])
);
expect(pair.verify(MESSAGE, signature)).toBe(true);
});
});
});
8 changes: 7 additions & 1 deletion packages/keyring/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,13 @@ export default class Keyring implements KeyringInstance {
}
}

return this.addFromSeed(keyFromPath(seed, path, type), meta, type);
const { publicKey } = this.isSr25519
? schnorrkelFromSeed(seed)
: naclFromSeed(seed);

return this.addPair(
createPair(type, keyFromPath({ publicKey, seed }, path, type), meta, null)
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/keyring/src/pair/decode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import testingPairs from '../testingPairs';

const keyring = testingPairs({ type: 'ed25519' });
const keyring = testingPairs({ type: 'ed25519' }, false);

describe('decode', () => {
it('fails when no data provided', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/keyring/src/pair/encode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { PKCS8_DIVIDER, PKCS8_HEADER } from './defaults';
const PKCS8_LENGTH = PKCS8_DIVIDER.length + PKCS8_HEADER.length + 64;
const ENCODED_LENGTH = 125;

const keyring = testingPairs({ type: 'ed25519' });
const keyring = testingPairs({ type: 'ed25519' }, false);

describe('encode', () => {
it('returns PKCS8 when no passphrase supplied', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/keyring/src/pair/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import testingPairs from '../testingPairs';

const keyring = testingPairs({ type: 'ed25519' });
const keyring = testingPairs({ type: 'ed25519' }, false);

describe('pair', () => {
const SIGNATURE = new Uint8Array([80, 191, 198, 147, 225, 207, 75, 88, 126, 39, 129, 109, 191, 38, 72, 181, 75, 254, 81, 143, 244, 79, 237, 38, 236, 141, 28, 252, 134, 26, 169, 234, 79, 33, 153, 158, 151, 34, 175, 188, 235, 20, 35, 135, 83, 120, 139, 211, 233, 130, 1, 208, 201, 215, 73, 80, 56, 98, 185, 196, 11, 8, 193, 14]);
Expand Down
10 changes: 2 additions & 8 deletions packages/keyring/src/pair/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ export default function createPair (type: KeypairType, { publicKey, seed }: Pair
let secretKey: Uint8Array | undefined;

if (seed) {
const pair = fromSeed(type, seed);

publicKey = pair.publicKey;
secretKey = pair.secretKey;
secretKey = fromSeed(type, seed).secretKey;
}

return {
Expand All @@ -80,8 +77,7 @@ export default function createPair (type: KeypairType, { publicKey, seed }: Pair
const decoded = decode(passphrase, _encoded || encoded);

publicKey = decoded.publicKey;
seed = decoded.seed;
secretKey = fromSeed(type, seed).secretKey;
secretKey = fromSeed(type, decoded.seed).secretKey;
},
encodePkcs8: (passphrase?: string): Uint8Array =>
encode({ publicKey, seed }, passphrase),
Expand All @@ -101,8 +97,6 @@ export default function createPair (type: KeypairType, { publicKey, seed }: Pair
sign(type, message, { publicKey, secretKey }),
toJson: (passphrase?: string): KeyringPair$Json =>
toJson(type, { meta, publicKey }, encode({ publicKey, seed }, passphrase), !!passphrase),
toType: (type: KeypairType): KeyringPair =>
createPair(type, { publicKey, seed }, meta, null),
verify: (message: Uint8Array, signature: Uint8Array): boolean =>
verify(type, message, signature, publicKey)
};
Expand Down
2 changes: 0 additions & 2 deletions packages/keyring/src/pair/nobody.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export default function everybody (): KeyringPair {
new Uint8Array(64),
toJson: (passphrase?: string): KeyringPair$Json =>
json,
toType: (): KeyringPair =>
everybody(),
verify: (message: Uint8Array, signature: Uint8Array): boolean =>
false
};
Expand Down
2 changes: 1 addition & 1 deletion packages/keyring/src/pair/toJson.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import testingPairs from '../testingPairs';

const keyring = testingPairs({ type: 'ed25519' });
const keyring = testingPairs({ type: 'ed25519' }, false);

describe('toJson', () => {
it('creates an unencoded output with no passphrase', () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/keyring/src/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ const SEEDS = ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve', 'Ferdie'];
* @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, isHdKd: boolean = false): KeyringInstance {
export default function testKeyring (options?: KeyringOptions, isDerived: boolean = true): KeyringInstance {
const keyring = new Keyring(options);

SEEDS.forEach((entry) => {
const phrase = isHdKd
const phrase = isDerived
? `${DEV_PHRASE}//${entry}`
: entry;
const pair = keyring.addFromUri(phrase, {
isTesting: true,
name: entry.toLowerCase()
});
}, isDerived ? 'sr25519' : 'ed25519');

pair.lock = () => {
// we don't have lock/unlock functionality here
Expand Down
27 changes: 15 additions & 12 deletions packages/keyring/src/testingPairs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

import { cryptoWaitReady } from '@polkadot/util-crypto/index';
import testingPairs from './testingPairs';

describe('testing', () => {
it('creates without failing', () => {
beforeEach(async () => {
await cryptoWaitReady();
});

it.skip('creates without failing', () => {
expect(
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 (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');
});
it('has the correct address for Alice (HDKD)', () => {
expect(
testingPairs({ type: 'ed25519' }, true).alice.address()
).toEqual('5CfbEZ9EvFsdZ2yHPt5BsSsGpM9mZPkahsZm6WeZE87mJKKT');
});
});
4 changes: 2 additions & 2 deletions packages/keyring/src/testingPairs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ type TestKeyringMap = {
[index: string]: KeyringPair
};

export default function testKeyringPairs (options?: KeyringOptions, isHdKd: boolean = false): TestKeyringMap {
const keyring = createKeyring(options, isHdKd);
export default function testKeyringPairs (options?: KeyringOptions, isDerived: boolean = true): TestKeyringMap {
const keyring = createKeyring(options, isDerived);
const pairs = keyring.getPairs();

return pairs.reduce((result, pair) => {
Expand Down
1 change: 0 additions & 1 deletion packages/keyring/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export interface KeyringPair {
setMeta: (meta: KeyringPair$Meta) => void;
sign (message: Uint8Array): Uint8Array;
toJson (passphrase?: string): KeyringPair$Json;
toType (type: KeypairType): KeyringPair;
verify (message: Uint8Array, signature: Uint8Array): boolean;
}

Expand Down
2 changes: 1 addition & 1 deletion packages/util-crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"homepage": "https://github.com/polkadot-js/common/tree/master/packages/util-crypto#readme",
"dependencies": {
"@babel/runtime": "^7.3.4",
"@polkadot/schnorrkel-js": "^0.1.2-3",
"@polkadot/schnorrkel-js": "^0.2.0-2",
"@polkadot/util": "^0.37.1",
"@types/bip39": "^2.4.2",
"@types/pbkdf2": "^3.0.0",
Expand Down
12 changes: 6 additions & 6 deletions packages/util-crypto/src/key/fromPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
// 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 { KeypairType, Seedpair } 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) => {
export default function keyFromPath (seedpair: Seedpair, path: Array<DeriveJunction>, type: KeypairType): Seedpair {
return path.reduce((seedpair, junction) => {
return type === 'ed25519'
? keyHdkdEd15519(seed, junction)
: keyHdkdSr15519(seed, junction);
}, seed);
? keyHdkdEd15519(seedpair, junction)
: keyHdkdSr15519(seedpair, junction);
}, seedpair);
}
20 changes: 12 additions & 8 deletions packages/util-crypto/src/key/hdkdEd25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@
// 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';
import { Seedpair } from '../types';

import blake2AsU8a from '../blake2/asU8a';
import DeriveJunction from './DeriveJunction';
import { assert } from '@polkadot/util';

const HDKD = compactAddLength(stringToU8a('Ed25519HDKD'));
import naclDerivePrivate from '../nacl/derivePrivate';
import naclKeypairFromSeed from '../nacl/keypair/fromSeed';
import DeriveJunction from './DeriveJunction';

export default function keyHdkdEd25519 (seed: Uint8Array, { chainCode, isHard }: DeriveJunction): Uint8Array {
export default function keyHdkdEd25519 ({ seed }: Seedpair, { chainCode, isHard }: DeriveJunction): Seedpair {
assert(isHard, 'A soft key was found in the path (and is unsupported)');

return blake2AsU8a(
u8aConcat(HDKD, seed, chainCode)
);
const derived = naclDerivePrivate(seed, chainCode);

return {
publicKey: naclKeypairFromSeed(derived).publicKey,
seed: derived
};
}
27 changes: 19 additions & 8 deletions packages/util-crypto/src/key/hdkdSr25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@
// 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';
import { Seedpair } from '../types';

import blake2AsU8a from '../blake2/asU8a';
import schnorrkelKeypairFromSeed from '../schnorrkel/keypair/fromSeed';
import schnorrkelDerivePrivate from '../schnorrkel/derivePrivate';
import schnorrkelDerivePublic from '../schnorrkel/derivePublic';
import schnorrkelSecretFromSeed from '../schnorrkel/secretFromSeed';
import DeriveJunction from './DeriveJunction';

const HDKD = stringToU8a('SchnorrRistrettoHDKD');
export default function keyHdkdSr25519 ({ seed, publicKey }: Seedpair, { chainCode, isSoft }: DeriveJunction): Seedpair {
if (isSoft) {
return {
publicKey: schnorrkelDerivePublic(publicKey, chainCode),
seed
};
}

// 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)
);
const secretKey = schnorrkelSecretFromSeed(seed);
const derived = schnorrkelDerivePrivate(secretKey, chainCode);

return {
publicKey: schnorrkelKeypairFromSeed(derived).publicKey,
seed: derived
};
}
15 changes: 15 additions & 0 deletions packages/util-crypto/src/nacl/derivePrivate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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 { compactAddLength, stringToU8a, u8aConcat } from '@polkadot/util';

import blake2AsU8a from '../blake2/asU8a';

const HDKD = compactAddLength(stringToU8a('Ed25519HDKD'));

export default function derivePrivate (secretKey: Uint8Array, chainCode: Uint8Array): Uint8Array {
return blake2AsU8a(
u8aConcat(HDKD, secretKey, chainCode)
);
}
2 changes: 1 addition & 1 deletion packages/util-crypto/src/nacl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
/**
* @summary Implements [NaCl](http://nacl.cr.yp.to/) secret-key authenticated encryption, public-key authenticated encryption, hashing, and public-key signatures
*/

export { default as naclDecrypt } from './decrypt';
export { default as derivePrivate } from './derivePrivate';
export { default as naclEncrypt } from './encrypt';
export { default as naclKeypairFromRandom } from './keypair/fromRandom';
export { default as naclKeypairFromSecret } from './keypair/fromSecret';
Expand Down
15 changes: 15 additions & 0 deletions packages/util-crypto/src/schnorrkel/derivePrivate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// 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 { compactAddLength, stringToU8a, u8aConcat } from '@polkadot/util';

import blake2AsU8a from '../blake2/asU8a';

const HDKD = compactAddLength(stringToU8a('SchnorrRistrettoHDKD'));

export default function derivePrivate (secretKey: Uint8Array, chainCode: Uint8Array): Uint8Array {
return blake2AsU8a(
u8aConcat(HDKD, compactAddLength(secretKey), chainCode)
);
}
9 changes: 9 additions & 0 deletions packages/util-crypto/src/schnorrkel/derivePublic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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 schnorrkel from '@polkadot/schnorrkel-js';

export default function derivePublic (publicKey: Uint8Array, chainCode: Uint8Array): Uint8Array {
return schnorrkel.derivePublicSimple(publicKey, chainCode);
}
Loading

0 comments on commit 6049f09

Please sign in to comment.