diff --git a/packages/keyring/src/pair/index.spec.ts b/packages/keyring/src/pair/index.spec.ts index 0b48219e1..af7d5fb8c 100644 --- a/packages/keyring/src/pair/index.spec.ts +++ b/packages/keyring/src/pair/index.spec.ts @@ -2,14 +2,19 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { setSS58Format } from '@polkadot/util-crypto'; +import { cryptoWaitReady, setSS58Format } from '@polkadot/util-crypto'; +import { PAIRS } from '../testing'; import testingPairs from '../testingPairs'; import createPair from '.'; const keyring = testingPairs({ type: 'ed25519' }, false); describe('pair', (): void => { + beforeEach(async (): Promise => { + await cryptoWaitReady(); + }); + 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]); it('has a publicKey', (): void => { @@ -66,6 +71,8 @@ describe('pair', (): void => { expect(keyring.alice.address).toEqual( '7sGUeMak588SPY2YMmmuKUuLz7u2WQpf74F9dCFtSLB2td9d' ); + + setSS58Format(42); }); it('allows getting public key after decoding', (): void => { @@ -77,4 +84,13 @@ describe('pair', (): void => { expect(pair.isLocked).toEqual(false); }); + + it('allows derivation on the pair', (): void => { + const alice = createPair('sr25519', { publicKey: PAIRS[0].publicKey, secretKey: PAIRS[0].secretKey }, {}); + const stash = alice.derive('//stash'); + const soft = alice.derive('//funding/0'); + + expect(stash.publicKey).toEqual(PAIRS[1].publicKey); + expect(soft.address).toEqual('5ECQNn7UueWHPFda5qUi4fTmTtyCnPvGnuoyVVSj5CboJh9J'); + }); }); diff --git a/packages/keyring/src/pair/index.ts b/packages/keyring/src/pair/index.ts index 5971245c4..0af703835 100644 --- a/packages/keyring/src/pair/index.ts +++ b/packages/keyring/src/pair/index.ts @@ -6,8 +6,8 @@ import { Keypair, KeypairType } from '@polkadot/util-crypto/types'; import { KeyringPair, KeyringPair$Json, KeyringPair$Meta, SignOptions } from '../types'; import { PairInfo } from './types'; -import { u8aConcat } from '@polkadot/util'; -import { encodeAddress, naclKeypairFromSeed as naclFromSeed, naclSign, naclVerify, schnorrkelKeypairFromSeed as schnorrkelFromSeed, schnorrkelSign, schnorrkelVerify } from '@polkadot/util-crypto'; +import { u8aConcat, assert } from '@polkadot/util'; +import { encodeAddress, keyExtractPath, keyFromPath, naclKeypairFromSeed as naclFromSeed, naclSign, naclVerify, schnorrkelKeypairFromSeed as schnorrkelFromSeed, schnorrkelSign, schnorrkelVerify } from '@polkadot/util-crypto'; import decode from './decode'; import encode from './encode'; @@ -87,10 +87,21 @@ const verify = (type: KeypairType, message: Uint8Array, signature: Uint8Array, p */ export default function createPair (type: KeypairType, { publicKey, secretKey }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null): KeyringPair { return { - type, get address (): string { return encodeAddress(publicKey); }, + get meta (): KeyringPair$Meta { + return meta; + }, + get isLocked (): boolean { + return !secretKey || secretKey.length === 0 || isEmpty(secretKey); + }, + get publicKey (): Uint8Array { + return publicKey; + }, + get type (): KeypairType { + return type; + }, decodePkcs8: (passphrase?: string, _encoded?: Uint8Array | null): void => { const decoded = decode(passphrase, _encoded || encoded); @@ -104,20 +115,19 @@ export default function createPair (type: KeypairType, { publicKey, secretKey }: secretKey = pair.secretKey; } }, + derive: (suri: string, meta?: KeyringPair$Meta): KeyringPair => { + assert(secretKey, 'Cannot derive on a locked keypair'); + + const { path } = keyExtractPath(suri); + const derived = keyFromPath({ publicKey, secretKey: secretKey as Uint8Array }, path, type); + + return createPair(type, derived, meta, null); + }, encodePkcs8: (passphrase?: string): Uint8Array => encode({ publicKey, secretKey }, passphrase), - get meta (): KeyringPair$Meta { - return meta; - }, - get isLocked (): boolean { - return !secretKey || secretKey.length === 0 || isEmpty(secretKey); - }, lock: (): void => { secretKey = new Uint8Array(0); }, - get publicKey (): Uint8Array { - return publicKey; - }, setMeta: (additional: KeyringPair$Meta): void => { meta = { ...meta, ...additional }; }, diff --git a/packages/keyring/src/pair/nobody.ts b/packages/keyring/src/pair/nobody.ts index 46b3bd0f0..6f90828ab 100644 --- a/packages/keyring/src/pair/nobody.ts +++ b/packages/keyring/src/pair/nobody.ts @@ -24,13 +24,16 @@ const json: KeyringPair$Json = { }; export default function everybody (): KeyringPair { - return { + const pair: KeyringPair = { type: 'ed25519', address, // eslint-disable-next-line @typescript-eslint/no-unused-vars decodePkcs8: (passphrase?: string, encoded?: Uint8Array): void => undefined, // eslint-disable-next-line @typescript-eslint/no-unused-vars + derive: (suri: string, meta?: KeyringPair$Meta): KeyringPair => + pair, + // eslint-disable-next-line @typescript-eslint/no-unused-vars encodePkcs8: (passphrase?: string): Uint8Array => new Uint8Array(0), meta, @@ -52,4 +55,6 @@ export default function everybody (): KeyringPair { verify: (message: Uint8Array, signature: Uint8Array): boolean => false }; + + return pair; } diff --git a/packages/keyring/src/testing.ts b/packages/keyring/src/testing.ts index f91251d76..5e073948f 100644 --- a/packages/keyring/src/testing.ts +++ b/packages/keyring/src/testing.ts @@ -21,7 +21,7 @@ interface PairDef { // keyring is for testing - what happens is that in most cases the keyring is initialises // before anything else. Since the sr25519 crypto is async, this creates problems with // adding the keys when only the keyring is used. -const PAIRS: PairDef[] = [ +export const PAIRS: PairDef[] = [ { seed: 'Alice', secretKey: hexToU8a('0x98319d4ff8a9508c4bb0cf0b5a78d760a0b2082c02775e6e82370816fedfff48925a225d97aa00682d6a59b95b18780c10d7032336e88f3442b42361f4a66011'), diff --git a/packages/keyring/src/types.ts b/packages/keyring/src/types.ts index 29827d29d..20933d4e2 100644 --- a/packages/keyring/src/types.ts +++ b/packages/keyring/src/types.ts @@ -38,15 +38,16 @@ export interface SignOptions { } export interface KeyringPair { + readonly address: string; + readonly meta: KeyringPair$Meta; + readonly isLocked: boolean; + readonly publicKey: Uint8Array; readonly type: KeypairType; - address: string; decodePkcs8: (passphrase?: string, encoded?: Uint8Array) => void; + derive: (suri: string, meta?: KeyringPair$Meta) => KeyringPair; encodePkcs8: (passphrase?: string) => Uint8Array; - meta: KeyringPair$Meta; - isLocked: boolean; lock: () => void; - publicKey: Uint8Array; setMeta: (meta: KeyringPair$Meta) => void; sign (message: Uint8Array, options?: SignOptions): Uint8Array; toJson (passphrase?: string): KeyringPair$Json;