Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0480bfcbc5
|
||
|
|
a5157ef592
|
||
|
|
e1d88c6f63
|
+1
-2
@@ -31,8 +31,7 @@
|
||||
"deno": "yarn polkadot-dev-deno-map && yarn build && deno check --import-map=import_map.json mod.ts",
|
||||
"lint": "polkadot-dev-run-lint",
|
||||
"postinstall": "polkadot-dev-yarn-only",
|
||||
"test": "polkadot-dev-run-test --env browser ^mnemonic/toMiniSecretCmp",
|
||||
"test:mnemonicCmp": "polkadot-dev-run-test --env browser mnemonic/toMiniSecretCmp",
|
||||
"test": "polkadot-dev-run-test --env browser",
|
||||
"test:node": "polkadot-dev-run-test --env browser ^mnemonic/toMiniSecretCmp",
|
||||
"test:one": "polkadot-dev-run-test --env browser"
|
||||
},
|
||||
|
||||
@@ -18,12 +18,12 @@
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "14.0.3-quantus.2",
|
||||
"version": "14.0.3-quantus.3",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@polkadot/util": "14.0.3",
|
||||
"@polkadot/util-crypto": "14.0.3-quantus.2",
|
||||
"@quantus/crypto": "^0.1.0",
|
||||
"@polkadot/util-crypto": "14.0.3-quantus.3",
|
||||
"@quantus/crypto": "^0.3.0",
|
||||
"tslib": "^2.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,609 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import type { KeyringPair$Json } from './types.js';
|
||||
|
||||
import { hexToU8a, stringToU8a } from '@polkadot/util';
|
||||
import { base64Decode, cryptoWaitReady, encodeAddress, mnemonicGenerate, randomAsU8a, setSS58Format } from '@polkadot/util-crypto';
|
||||
import * as languages from '@polkadot/util-crypto/mnemonic/wordlists/index';
|
||||
|
||||
import { decodePair } from './pair/decode.js';
|
||||
import Keyring from './index.js';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('keypair', (): void => {
|
||||
describe('ed25519', (): void => {
|
||||
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]);
|
||||
const seedOne = stringToU8a('12345678901234567890123456789012');
|
||||
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42, type: 'ed25519' });
|
||||
|
||||
keyring.addFromSeed(seedOne, {});
|
||||
});
|
||||
|
||||
it('adds the pair', (): void => {
|
||||
expect(
|
||||
keyring.addFromSeed(seedTwo, {}).publicKey
|
||||
).toEqual(publicKeyTwo);
|
||||
});
|
||||
|
||||
it('creates via a dev seed', (): void => {
|
||||
expect(
|
||||
keyring.addFromUri('//Alice').address
|
||||
).toEqual('5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu');
|
||||
});
|
||||
|
||||
it('creates a ed25519 pair via mnemonicToSeed', (): void => {
|
||||
expect(
|
||||
keyring.addFromUri(
|
||||
'seed sock milk update focus rotate barely fade car face mechanic mercy'
|
||||
).address
|
||||
).toEqual('5DkQP32jP4DVJLWWBRBoZF2tpWjqFrcrTBo6H5NcSk7MxKCC');
|
||||
});
|
||||
|
||||
it('adds from a mnemonic, with correct ss58', (): void => {
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
setSS58Format(20); // this would not be used
|
||||
keyring.setSS58Format(2); // this would be used
|
||||
|
||||
const pair = keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {});
|
||||
|
||||
expect(pair.address).toEqual('HSLu2eci2GCfWkRimjjdTXKoFSDL3rBv5Ey2JWCBj68cVZj');
|
||||
expect(encodeAddress(pair.publicKey)).toEqual('35cDYtPsdG1HUa2n2MaARgJyRz1WKMBZK1DL6c5cX7nugQh1');
|
||||
});
|
||||
|
||||
it('allows publicKeys retrieval', (): void => {
|
||||
keyring.addFromSeed(seedTwo, {});
|
||||
|
||||
expect(
|
||||
keyring.getPublicKeys()
|
||||
).toEqual([publicKeyOne, publicKeyTwo]);
|
||||
});
|
||||
|
||||
it('allows retrieval of a specific item', (): void => {
|
||||
expect(
|
||||
keyring.getPair(publicKeyOne).publicKey
|
||||
).toEqual(publicKeyOne);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
expect(
|
||||
keyring.addFromJson(
|
||||
JSON.parse('{"address":"5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua","encoded":"0xb4a14995d25ab609f3686e9fa45f1fb237cd833f33f00d4b12c51858ca070d96972e47d73aae5eeb0fc06f923826cf0943fdb02c2c2ee30ef52a7912663053940d1da4da66b3a3f520ae07422c1c94b2d95690fca9d1f4a997623bb2923a8833280e19e7f72c3c5cfa343974e60e2b3dc53b404fdaf330756daad5e4e3","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"0"},"meta":{"isTesting":true,"name":"alice"}}') as KeyringPair$Json
|
||||
).publicKey
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
});
|
||||
|
||||
it('signs and verifies', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE);
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('signs and verifies (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE, { withType: true });
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sr25519', (): void => {
|
||||
const publicKeyOne = 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]);
|
||||
const publicKeyTwo = hexToU8a('0x44a996beb1eef7bdcab976ab6d2ca26104834164ecf28fb375600576fcc6eb0f');
|
||||
const seedOne = stringToU8a('12345678901234567890123456789012');
|
||||
const seedTwo = hexToU8a('0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60');
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42, type: 'sr25519' });
|
||||
|
||||
keyring.addFromSeed(seedOne, {});
|
||||
});
|
||||
|
||||
it('creates with dev phrase when only path specified', (): void => {
|
||||
expect(
|
||||
keyring.createFromUri('//Alice').address
|
||||
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
});
|
||||
|
||||
it('creates with integer derivations', (): void => {
|
||||
// MAX_SAFE_INTEGER
|
||||
expect(
|
||||
keyring.createFromUri('//9007199254740991').address
|
||||
).toEqual('5CDsyNZyqxLpHnTvknr68anUcYoBFjZbFKiEJJf4prB75Uog');
|
||||
|
||||
// MAX_SAFE_INTEGER + extra digits
|
||||
expect(
|
||||
keyring.createFromUri('//900719925474099999').address
|
||||
).toEqual('5GHj2D7RG2m2DXYwGSDpXwuuxn53G987i7p2EQVDqP4NYu4q');
|
||||
});
|
||||
|
||||
it('creates via dev seed (2-byte encoding)', (): void => {
|
||||
keyring.setSS58Format(252);
|
||||
|
||||
expect(
|
||||
keyring.addFromUri('//Alice').address
|
||||
).toEqual('xw8P6urbSAronL3zZFB7dg8p7LLSgKCUFDUgjohnf1iP434ic');
|
||||
});
|
||||
|
||||
it('adds the pair', (): void => {
|
||||
expect(
|
||||
keyring.addFromSeed(seedTwo, {}).publicKey
|
||||
).toEqual(publicKeyTwo);
|
||||
});
|
||||
|
||||
it('adds from a mnemonic', (): void => {
|
||||
keyring.setSS58Format(2);
|
||||
|
||||
expect(
|
||||
keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {}).address
|
||||
).toEqual('FSjXNRT2K1R5caeHLPD6WMrqYUpfGZB7ua8W89JFctZ1YqV');
|
||||
});
|
||||
|
||||
it('allows publicKeys retrieval', (): void => {
|
||||
keyring.addFromSeed(seedTwo, {});
|
||||
|
||||
expect(
|
||||
keyring.getPublicKeys()
|
||||
).toEqual([publicKeyOne, publicKeyTwo]);
|
||||
});
|
||||
|
||||
it('allows retrieval of a specific item', (): void => {
|
||||
expect(
|
||||
keyring.getPair(publicKeyOne).publicKey
|
||||
).toEqual(publicKeyOne);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
expect(
|
||||
keyring.addFromJson(
|
||||
JSON.parse('{"address":"5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua","encoded":"0xb4a14995d25ab609f3686e9fa45f1fb237cd833f33f00d4b12c51858ca070d96972e47d73aae5eeb0fc06f923826cf0943fdb02c2c2ee30ef52a7912663053940d1da4da66b3a3f520ae07422c1c94b2d95690fca9d1f4a997623bb2923a8833280e19e7f72c3c5cfa343974e60e2b3dc53b404fdaf330756daad5e4e3","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"0"},"meta":{"isTesting":true,"name":"alice"}}') as KeyringPair$Json
|
||||
).publicKey
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
});
|
||||
|
||||
it('signs and verifies', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE);
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('signs and verifies (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(publicKeyOne);
|
||||
const signature = pair.sign(MESSAGE, { withType: true });
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ecdsa', (): void => {
|
||||
const seedOne = 'potato act energy ahead stone taxi receive fame gossip equip chest round';
|
||||
const seedTwo = hexToU8a('0x3c74be003bd9a876be439949ccf2b292bd966c94959a689173b295b326cd6da7');
|
||||
const publicKeyOne = hexToU8a('0x02c6b6c664db5ef505477bba1cf2f1789c98796b9bb5fa21abd0ac4589bed980e7');
|
||||
const publicKeyTwo = hexToU8a('0x021da683b913fb28c979ba3e5f1881415cef4b1f58a5d05ed3610a2995e7b4943c');
|
||||
const addressKeyOne = hexToU8a('0x0cfd0dd2c59a9987b9848919163931b6a42283ffd3d91e92c98b522525a7038f');
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42, type: 'ecdsa' });
|
||||
|
||||
keyring.addFromMnemonic(seedOne, {});
|
||||
});
|
||||
|
||||
it('creates with dev phrase when only path specified', (): void => {
|
||||
expect(
|
||||
keyring.createFromUri('//Alice').address
|
||||
).toEqual('5C7C2Z5sWbytvHpuLTvzKunnnRwQxft1jiqrLD5rhucQ5S9X');
|
||||
});
|
||||
|
||||
it('adds the pair', (): void => {
|
||||
expect(
|
||||
keyring.addFromSeed(seedTwo, {}).publicKey
|
||||
).toEqual(publicKeyTwo);
|
||||
});
|
||||
|
||||
it('adds from a mnemonic', (): void => {
|
||||
keyring.setSS58Format(2);
|
||||
|
||||
expect(
|
||||
keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card').address
|
||||
).toEqual('DrRE1KAcs4pCicX8yJPh7YxkLPQ2vXnCFSVRPQfx38KjEFe');
|
||||
});
|
||||
|
||||
it('allows publicKeys retrieval', (): void => {
|
||||
keyring.addFromSeed(seedTwo, {});
|
||||
|
||||
expect(
|
||||
keyring.getPublicKeys()
|
||||
).toEqual([publicKeyOne, publicKeyTwo]);
|
||||
});
|
||||
|
||||
it('allows retrieval of a specific item', (): void => {
|
||||
expect(
|
||||
keyring.getPair(addressKeyOne).publicKey
|
||||
).toEqual(publicKeyOne);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
expect(
|
||||
keyring.addFromJson(
|
||||
JSON.parse('{"address":"5DzMsaYFhmpRdErWrP6K6PD7UXzYoeETToSBUrZSvxasqWRz","encoded":"0xa192d39b42bc1601bf61df31039a554228593fadf870bc837b658a5114627aca199fff596260c95fe8994c66a47636cf0270aa08f402ba5541038753960d00e6c3af5e239ec58fb1eef3db7d6bc266f4853bdfe4ed17122d9092d879014d53980d2ee57f6f55a88c38836447d8645008e8815379626addc8f81f80cd49a2","encoding":{"content":"pkcs8","type":"xsalsa20-poly1305","version":"2"},"meta":{}}') as KeyringPair$Json
|
||||
).address
|
||||
).toEqual('5DzMsaYFhmpRdErWrP6K6PD7UXzYoeETToSBUrZSvxasqWRz');
|
||||
});
|
||||
|
||||
it('allows creation from JSON', (): void => {
|
||||
keyring.setSS58Format(2);
|
||||
const pair = keyring.createFromJson(
|
||||
JSON.parse('{"address":"0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad","encoded":"qIhAhKqtf2iyEoWEr8nmBdksSI8EHHCpgJHToqd6Pl8AgAAAAQAAAAgAAADDZ//fj/BRRj+0+bl1KAlYgoPJp6nEUwiw0fVqO2BW4mjEgQ+iWwJEgDf1JUtecbzOlfhTXBzqX/dIYzLgUADrF4EFEPpboCWiU1iN7W/3DM1cOTRVvTGcbdIqW//z3axhz961qzeJVUIFgllwGe/euLUPIlKbIkiN/CsRYdQ=","encoding":{"content":["pkcs8","ecdsa"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"ecdsa","tags":[],"whenCreated":1600925898271}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(pair.address).toEqual('DHL8HKFuTTR55JzzLmkJRCAfPBbuevKaT9cXikxbEV97Ko8');
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad'));
|
||||
});
|
||||
|
||||
it('fails toJson() when password is incorrect', (): void => {
|
||||
const pair = keyring.createFromJson(
|
||||
JSON.parse('{"address":"0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad","encoded":"qIhAhKqtf2iyEoWEr8nmBdksSI8EHHCpgJHToqd6Pl8AgAAAAQAAAAgAAADDZ//fj/BRRj+0+bl1KAlYgoPJp6nEUwiw0fVqO2BW4mjEgQ+iWwJEgDf1JUtecbzOlfhTXBzqX/dIYzLgUADrF4EFEPpboCWiU1iN7W/3DM1cOTRVvTGcbdIqW//z3axhz961qzeJVUIFgllwGe/euLUPIlKbIkiN/CsRYdQ=","encoding":{"content":["pkcs8","ecdsa"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"ecdsa","tags":[],"whenCreated":1600925898271}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(
|
||||
() => pair.toJson('invalid')
|
||||
).toThrow(/Unable to decode using the supplied passphrase/);
|
||||
});
|
||||
|
||||
it('pass toJson() when password is correct', (): void => {
|
||||
const pair = keyring.createFromJson(
|
||||
JSON.parse('{"address":"0x02fde629668eb2bcc7d748f40a7e597f7c7b363498ff3db31f03ce4854937883ad","encoded":"qIhAhKqtf2iyEoWEr8nmBdksSI8EHHCpgJHToqd6Pl8AgAAAAQAAAAgAAADDZ//fj/BRRj+0+bl1KAlYgoPJp6nEUwiw0fVqO2BW4mjEgQ+iWwJEgDf1JUtecbzOlfhTXBzqX/dIYzLgUADrF4EFEPpboCWiU1iN7W/3DM1cOTRVvTGcbdIqW//z3axhz961qzeJVUIFgllwGe/euLUPIlKbIkiN/CsRYdQ=","encoding":{"content":["pkcs8","ecdsa"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"ecdsa","tags":[],"whenCreated":1600925898271}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(
|
||||
() => pair.toJson('testing')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('encodes a pair toJSON (and decodes)', (): void => {
|
||||
const pair = keyring.createFromUri('moral movie very draw assault whisper awful rebuild speed purity repeat card');
|
||||
const json = pair.toJson('password');
|
||||
|
||||
expect(json.address).toEqual('0x03ddca309bd5fedd01f914d6fb76f23aa848a2a520802159215dba5085d7863619');
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'ecdsa'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
|
||||
const newPair = keyring.createFromJson(json);
|
||||
|
||||
expect(newPair.publicKey).toEqual(pair.publicKey);
|
||||
expect(
|
||||
() => newPair.unlock('password')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('signs and verifies', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(addressKeyOne);
|
||||
const signature = pair.sign(MESSAGE);
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('signs and verifies (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
const pair = keyring.getPair(addressKeyOne);
|
||||
const signature = pair.sign(MESSAGE, { withType: true });
|
||||
|
||||
expect(pair.verify(MESSAGE, signature, pair.publicKey)).toBe(true);
|
||||
expect(pair.verify(MESSAGE, signature, randomAsU8a())).toBe(false);
|
||||
expect(pair.verify(new Uint8Array(), signature, pair.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ethereum', (): void => {
|
||||
// combine mnemonic with derivation path
|
||||
const PHRASE = 'seed sock milk update focus rotate barely fade car face mechanic mercy' + '/m/44\'/60\'/0\'/0/0';
|
||||
const PRIV_KEY_ONE = '0x070dc3117300011918e26b02176945cc15c3d548cf49fd8418d97f93af699e46';
|
||||
const ETH_ADDRESS_ONE = '0x31ea8795EE32D782C8ff41a5C68Dcbf0F5B27f6d';
|
||||
const ETH_ADDRESS_TWO = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
|
||||
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ type: 'ethereum' });
|
||||
});
|
||||
|
||||
it('creates with dev phrase from the private key', (): void => {
|
||||
const pair = keyring.addFromSeed(hexToU8a(PRIV_KEY_ONE));
|
||||
|
||||
expect(
|
||||
pair.address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase from the private key in createFromUri', (): void => {
|
||||
const pair = keyring.createFromUri(PRIV_KEY_ONE);
|
||||
|
||||
expect(
|
||||
pair.address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase with derivation path specified', (): void => {
|
||||
const pair = keyring.createFromUri(PHRASE);
|
||||
|
||||
expect(
|
||||
pair.address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase with derivation path specified - addFromUri', (): void => {
|
||||
expect(
|
||||
keyring.addFromUri(PHRASE).address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('creates with dev phrase with derivation path specified - addFromUri with type', (): void => {
|
||||
const keyringUntyped = new Keyring();
|
||||
|
||||
expect(
|
||||
keyringUntyped.addFromUri(PHRASE, {}, 'ethereum').address
|
||||
).toEqual(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('encodes a pair toJSON (and decodes)', (): void => {
|
||||
const pair = keyring.createFromUri(PHRASE);
|
||||
const json = pair.toJson('password');
|
||||
|
||||
expect(json.address).toEqual('0x0381351b1b46d2602b0992bb5d5531f9c1696b0812feb2534b6884adc47e2e1d8b'); // this is the public key (different from address for ethereum)
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'ethereum'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
|
||||
const newPair = keyring.createFromJson(json);
|
||||
|
||||
expect(newPair.publicKey).toEqual(pair.publicKey);
|
||||
expect(
|
||||
() => newPair.unlock('password')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('encodes a pair toJSON and back', (): void => {
|
||||
const pairOriginal = keyring.createFromUri(PHRASE);
|
||||
const json = pairOriginal.toJson('password');
|
||||
const pair = keyring.addFromJson(
|
||||
json
|
||||
);
|
||||
|
||||
expect(pair.address).toEqual(ETH_ADDRESS_ONE);
|
||||
|
||||
pair.decodePkcs8('password');
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe(ETH_ADDRESS_ONE);
|
||||
});
|
||||
|
||||
it('allows adding from JSON', (): void => {
|
||||
const pair = keyring.addFromJson(
|
||||
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
|
||||
expect(pair.address).toEqual(ETH_ADDRESS_TWO);
|
||||
|
||||
pair.decodePkcs8('password');
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.publicKey).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'));
|
||||
expect(pair.address).toBe(ETH_ADDRESS_TWO);
|
||||
});
|
||||
|
||||
it('allows for signing/verification', (): void => {
|
||||
const MESSAGE = stringToU8a('just some test message');
|
||||
const signer = keyring.createFromUri(PHRASE);
|
||||
const verifier = keyring.addFromJson(
|
||||
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
const signature = signer.sign(MESSAGE);
|
||||
const dummyPublic = verifier.publicKey.slice();
|
||||
|
||||
dummyPublic[dummyPublic.length - 1] = 0;
|
||||
|
||||
expect(verifier.verify(MESSAGE, signature, signer.publicKey)).toBe(true);
|
||||
expect(verifier.verify(MESSAGE, signature, dummyPublic)).toBe(false);
|
||||
expect(verifier.verify(new Uint8Array(), signature, signer.publicKey)).toBe(false);
|
||||
});
|
||||
|
||||
it('allows for signing/verification (withType)', (): void => {
|
||||
const MESSAGE = stringToU8a('just some test message');
|
||||
const signer = keyring.createFromUri(PHRASE);
|
||||
const verifier = keyring.addFromJson(
|
||||
JSON.parse('{"address":"KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou","encoded":"U8qFEaghhmNV2PgFhjqzmhyUy37Ok7abfFU2MNsBd0sAgAAAAQAAAAgAAAA3+NniKogzNphiMNueB1X0sGA07B6CaXWfpXPx45iSXoTTprwzU5mOoSqUWO0GKHROI72LN+uJ8Yfv6Ll6JOOV3VPKfoVoFmYm+zDrrMPa0gk5E5kUuSijxADcE6zUrliPVr0Ix/qaghu5SJ7RtWDQLBf4Hp86SJ8Gg6gTSSk=","encoding":{"content":["pkcs8","ethereum"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{}}') as KeyringPair$Json
|
||||
);
|
||||
|
||||
const signature = signer.sign(MESSAGE, { withType: true });
|
||||
const dummyPublic = verifier.publicKey.slice();
|
||||
|
||||
dummyPublic[dummyPublic.length - 1] = 0;
|
||||
|
||||
expect(verifier.verify(MESSAGE, signature, signer.publicKey)).toBe(true);
|
||||
expect(verifier.verify(MESSAGE, signature, dummyPublic)).toBe(false);
|
||||
expect(verifier.verify(new Uint8Array(), signature, signer.publicKey)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('raw pair add/create', (): void => {
|
||||
const json = JSON.parse('{"address":"5PjeoaQzCoYbSi42aQRKB3Sx18StCaEAzCbGEEbWbZyfKS3H","encoded":"JQUl8ZpoXv2OMkL9TPylLmcIye2cYhaS9INICbFgZTsAgAAAAQAAAAgAAAAr/0hJOOzokIdBG71TstigLABX9D5xGD7L37ySxtjDrVRg26LL90jLQ47quT9o3bq6ppXMVL6USk7Q4p3WU66bojTFuCDyhpYRhNbUqU6s0rD3S4bhv9lG+pG9vQ4eD5PVQUvxdANmJpYuDg45nrTmsMC5AHGdFGkHW/LHnkmbFid1cvPYkdiBoef5CIEdoly512pxMupVxnJWF1NT","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"name":"hello"}}') as KeyringPair$Json;
|
||||
const decoded = decodePair('1', base64Decode(json.encoded), json.encoding.type);
|
||||
const keyring = new Keyring({ ss58Format: 44 });
|
||||
|
||||
it('creates a pair from a private/public combo', (): void => {
|
||||
const pair = keyring.createFromPair(decoded, json.meta, 'sr25519');
|
||||
|
||||
expect(pair.address).toEqual('5PjeoaQzCoYbSi42aQRKB3Sx18StCaEAzCbGEEbWbZyfKS3H');
|
||||
expect(pair.isLocked).toEqual(false);
|
||||
expect(pair.meta.name).toEqual('hello');
|
||||
});
|
||||
|
||||
it('adds a pair from a private/public combo', (): void => {
|
||||
keyring.addFromPair(decoded, json.meta, 'sr25519');
|
||||
|
||||
const pair = keyring.getPairs()[0];
|
||||
|
||||
expect(pair.address).toEqual('5PjeoaQzCoYbSi42aQRKB3Sx18StCaEAzCbGEEbWbZyfKS3H');
|
||||
expect(pair.isLocked).toEqual(false);
|
||||
expect(pair.meta.name).toEqual('hello');
|
||||
});
|
||||
});
|
||||
|
||||
describe('util', (): void => {
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42 });
|
||||
});
|
||||
|
||||
it('can re-encode an address to Polkadot live', (): void => {
|
||||
expect(
|
||||
keyring.encodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 0)
|
||||
).toEqual('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5');
|
||||
});
|
||||
|
||||
it('can re-encode an address to keyring default', (): void => {
|
||||
expect(
|
||||
keyring.encodeAddress('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5')
|
||||
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
});
|
||||
});
|
||||
|
||||
describe('version 2 JSON', (): void => {
|
||||
const PAIR = '{"address":"5CczAE5AmGrZ93MeVhha3Ywam7j9dKB7cArnH7gtrXcMFJvu","encoded":"0xee8f236e2ac3217ce689692a4afc612220dc77fddaed0482f8f95136a7c3e034cccfbc495410a6e9b2439904974ed1d207abeca536ff6985ceb78edeeb3dc343e561c184c488101af8811d1331430b4ccf0e96ef507132e5132964e8564232e7100d973c5bee7b231dd0c8ad5273f3501515a422c8d7ed9d20a73c0ed17c98ee4588e54844bb73052dcad81f7a1094613d63c162fec7446c88b1fae70e","encoding":{"content":["pkcs8","sr25519"],"type":"xsalsa20-poly1305","version":"2"},"meta":{"genesisHash":"0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e","name":"json v2","tags":[],"whenCreated":1595243159596}}';
|
||||
const PASS2 = 'versionTwo';
|
||||
const PASS3 = 'versionThree';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 42 });
|
||||
});
|
||||
|
||||
it('can decode from a version 2 JSON file', (): void => {
|
||||
const pair = keyring.addFromJson(JSON.parse(PAIR) as KeyringPair$Json);
|
||||
|
||||
pair.decodePkcs8(PASS2);
|
||||
|
||||
const json = pair.toJson(PASS3);
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe('5CczAE5AmGrZ93MeVhha3Ywam7j9dKB7cArnH7gtrXcMFJvu');
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'sr25519'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
|
||||
pair.decodePkcs8(PASS3);
|
||||
|
||||
expect(pair.address).toEqual('5CczAE5AmGrZ93MeVhha3Ywam7j9dKB7cArnH7gtrXcMFJvu');
|
||||
});
|
||||
});
|
||||
|
||||
describe('version 3 JSON (hex)', (): void => {
|
||||
const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"0xcd238963070cc4d6806053ee1ac500c7add9c28732bb5d434a332f84a91d9be0008000000100000008000000cf630a1113941b350ddd06697e50399183162e5e9a0e893eafc7f5f4893a223dca5055706b9925b56fdb4304192143843da718e11717daf89cf4f4781f94fb443f61432f782d54280af9eec90bd3069c3cc2d957a42b7c18dc2e9497f623735518e0e49b58f8e4db2c09da3a45dbb935659d015fc94b946cba75b606a6ff7f4e823f6b049e2e6892026b49de02d6dbbd64646fe0933f537d9ea53a70be","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639}}';
|
||||
const PASS3 = 'version3';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 2 });
|
||||
});
|
||||
|
||||
it('can decode from a version 3 JSON file', (): void => {
|
||||
const pair = keyring.addFromJson(JSON.parse(PAIR) as KeyringPair$Json);
|
||||
|
||||
pair.decodePkcs8(PASS3);
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe('FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('version 3 JSON (base64)', (): void => {
|
||||
const PAIR = '{"address":"FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH","encoded":"ILjSgYaGvq1zaCz/kx+aqfLaHBjLXz0Qsmr6RnkOVU4AgAAAAQAAAAgAAAB5R2hm5kgXyc0NQYFxvMU4zCdjB+ugs/ibEooqCvuudbaeKn3Ee47NkCqU1ecOJV+eeaVn4W4dRvIpj5kGmQOGsewR+MiQ/B0G9NFh7JXV0qcPlk2QMNW1/mbJrTO4miqL448BSkP7ZOhUV6HFUpMt3B9HwjiRLN8RORcFp0ID/Azs4Jl/xOpXNzbgQGIffWgCIKTxN9N1ku6tdlG4","encoding":{"content":["pkcs8","sr25519"],"type":["scrypt","xsalsa20-poly1305"],"version":"3"},"meta":{"genesisHash":"0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe","name":"version3","tags":[],"whenCreated":1595277797639,"whenEdited":1595278378596}}';
|
||||
const PASS3 = 'version3';
|
||||
let keyring: Keyring;
|
||||
|
||||
beforeEach((): void => {
|
||||
keyring = new Keyring({ ss58Format: 2 });
|
||||
});
|
||||
|
||||
it('can decode from a version 3 JSON file', (): void => {
|
||||
const pair = keyring.addFromJson(JSON.parse(PAIR) as KeyringPair$Json);
|
||||
|
||||
pair.decodePkcs8(PASS3);
|
||||
|
||||
expect(pair.isLocked).toBe(false);
|
||||
expect(pair.address).toBe('FLiSDPCcJ6auZUGXALLj6jpahcP6adVFDBUQznPXUQ7yoqH');
|
||||
});
|
||||
});
|
||||
|
||||
describe('wordlist', (): void => {
|
||||
it('creates keypair from different wordlists mnemonics', (): void => {
|
||||
Object.keys(languages).forEach((language) => {
|
||||
const mnemonic = mnemonicGenerate(12, languages[language as keyof typeof languages]);
|
||||
const keyring = new Keyring({
|
||||
type: 'ed25519'
|
||||
});
|
||||
|
||||
expect(keyring.addFromMnemonic(
|
||||
mnemonic,
|
||||
{},
|
||||
'ed25519',
|
||||
languages[language as keyof typeof languages]
|
||||
)).toBeDefined();
|
||||
});
|
||||
});
|
||||
it('cannot create from invalid wordlist', (): void => {
|
||||
const mnemonic = mnemonicGenerate(12, languages.japanese);
|
||||
const keyring = new Keyring({
|
||||
type: 'ed25519'
|
||||
});
|
||||
|
||||
expect(() => keyring.addFromMnemonic(
|
||||
mnemonic,
|
||||
{},
|
||||
'ed25519',
|
||||
languages.english
|
||||
)).toThrow('Invalid bip39 mnemonic specified');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||
|
||||
import { contextForSpec } from '@quantus/crypto';
|
||||
|
||||
import { u8aConcat } from '@polkadot/util';
|
||||
import { cryptoWaitReady, decodeAddress, dilithiumSizes } from '@polkadot/util-crypto';
|
||||
|
||||
import { Keyring } from './index.js';
|
||||
import { createTestPairs } from './testingPairs.js';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
// Addresses `quantus developer create-test-wallets` prints for the dev accounts.
|
||||
const ALICE = 'qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7';
|
||||
const BOB = 'qzkYEQv8tQsmniZYdame3Cku18RL5g9bGK9Pdydq5TMPdpE3y';
|
||||
const CHARLIE = 'qzntBpmqHZF1jxC8KJKpuxcYuHST892jyXBqRctpAxd1WQ9BL';
|
||||
const MESSAGE = new Uint8Array([1, 2, 3, 4]);
|
||||
const PASSWORD = 'correct horse';
|
||||
|
||||
// Upstream's keyring specs exercised ed25519, sr25519, ecdsa and ethereum pairs.
|
||||
// Those types, and their primitives, are gone (quantus/common#6). What replaces
|
||||
// them is what a post-quantum-only keyring has to guarantee instead.
|
||||
describe('Keyring (post-quantum only)', (): void => {
|
||||
describe('refuses quantum-unsafe keys, and says why', (): void => {
|
||||
const unsafe = ['ed25519', 'sr25519', 'ecdsa', 'ethereum'] as unknown as KeypairType[];
|
||||
|
||||
for (const type of unsafe) {
|
||||
it(`at construction: ${type}`, (): void => {
|
||||
expect(() => new Keyring({ type })).toThrow(/not quantum-safe and cannot be held here/);
|
||||
});
|
||||
|
||||
it(`from a uri: ${type}`, (): void => {
|
||||
expect(() => new Keyring().createFromUri('//Alice', {}, type)).toThrow(/not quantum-safe/);
|
||||
});
|
||||
}
|
||||
|
||||
// The entry point that matters most: a backup file the user chose. This is
|
||||
// the shape of a real polkadot{.js} export of an ed25519 account.
|
||||
it('from a polkadot{.js} JSON backup', (): void => {
|
||||
const json = {
|
||||
address: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
||||
encoded: '0x00',
|
||||
encoding: { content: ['pkcs8', 'ed25519'], type: ['scrypt', 'xsalsa20-poly1305'], version: '3' },
|
||||
meta: { name: 'polkadot account' }
|
||||
};
|
||||
|
||||
expect(() => new Keyring().createFromJson(json as never)).toThrow(/ed25519 keys are not quantum-safe and cannot be held here/);
|
||||
});
|
||||
|
||||
it('and still refuses an unknown type as unknown', (): void => {
|
||||
expect(() => new Keyring({ type: 'dilithium44' as KeypairType })).toThrow(/found 'dilithium44'/);
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults to ML-DSA-65', (): void => {
|
||||
expect(new Keyring().type).toEqual('dilithium65');
|
||||
});
|
||||
|
||||
describe('test pairs are the Quantus dev accounts', (): void => {
|
||||
const pairs = createTestPairs({ ss58Format: 189 });
|
||||
|
||||
it('with the addresses the chain endows', (): void => {
|
||||
expect(pairs.crystal_alice.address).toEqual(ALICE);
|
||||
expect(pairs.dilithium_bob.address).toEqual(BOB);
|
||||
expect(pairs.crystal_charlie.address).toEqual(CHARLIE);
|
||||
expect(pairs.crystal_alice.type).toEqual('dilithium87');
|
||||
});
|
||||
|
||||
it('and a nobody pair of a type the keyring can hold', (): void => {
|
||||
expect(pairs.nobody.type).toEqual('dilithium65');
|
||||
});
|
||||
});
|
||||
|
||||
// Upstream decoded the address and passed it as the public key, which for
|
||||
// Substrate is the same bytes. Here the address is a hash of the key, so every
|
||||
// watch-only account reported the hash of its own address.
|
||||
it('adds a watch-only account at the address it was given', (): void => {
|
||||
const keyring = new Keyring({ ss58Format: 189 });
|
||||
const watched = keyring.addFromAddress(ALICE, { name: 'watched' });
|
||||
|
||||
expect(watched.address).toEqual(ALICE);
|
||||
expect(watched.addressRaw).toEqual(decodeAddress(ALICE));
|
||||
expect(keyring.getPair(ALICE).meta.name).toEqual('watched');
|
||||
});
|
||||
|
||||
describe('JSON round trip', (): void => {
|
||||
for (const type of ['dilithium65', 'dilithium87'] as const) {
|
||||
it(`${type}: encrypts, restores locked at the right address, and unlocks`, (): void => {
|
||||
const keyring = new Keyring({ ss58Format: 189 });
|
||||
const pair = keyring.addFromSeed(new Uint8Array(32).fill(7), { name: 'roundtrip' }, type);
|
||||
const restored = keyring.createFromJson(pair.toJson(PASSWORD));
|
||||
|
||||
expect(restored.isLocked).toBe(true);
|
||||
expect(restored.address).toEqual(pair.address);
|
||||
expect(() => restored.decodePkcs8('wrong')).toThrow();
|
||||
|
||||
restored.decodePkcs8(PASSWORD);
|
||||
|
||||
expect(restored.publicKey).toEqual(pair.publicKey);
|
||||
expect(restored.sign(MESSAGE, { context: contextForSpec(148) })).toEqual(pair.sign(MESSAGE, { context: contextForSpec(148) }));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe('verify', (): void => {
|
||||
const keyring = new Keyring({ ss58Format: 189 });
|
||||
const alice = keyring.addFromSeed(new Uint8Array(32), {}, 'dilithium87');
|
||||
const bob = keyring.addFromSeed(new Uint8Array(32).fill(1), {}, 'dilithium87');
|
||||
const context = contextForSpec(148);
|
||||
// what pair.sign returns: the chain's wire form, signature ‖ publicKey
|
||||
const signature = alice.sign(MESSAGE, { context });
|
||||
const bare = signature.slice(0, dilithiumSizes('dilithium87').signature);
|
||||
|
||||
it('accepts the signature ‖ publicKey form and a bare signature', (): void => {
|
||||
expect(signature).toEqual(u8aConcat(bare, alice.publicKey));
|
||||
expect(alice.verify(MESSAGE, signature, alice.publicKey, context)).toBe(true);
|
||||
expect(alice.verify(MESSAGE, bare, alice.publicKey, context)).toBe(true);
|
||||
});
|
||||
|
||||
it('fails under the wrong context, for the wrong signer, or for another message', (): void => {
|
||||
expect(alice.verify(MESSAGE, signature, alice.publicKey)).toBe(false);
|
||||
expect(alice.verify(MESSAGE, signature, bob.publicKey, context)).toBe(false);
|
||||
expect(alice.verify(new Uint8Array([9]), signature, alice.publicKey, context)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('has no derivation and no VRF', (): void => {
|
||||
const pair = new Keyring().addFromSeed(new Uint8Array(32));
|
||||
|
||||
expect(() => pair.derive('//1')).toThrow(/derive from the mnemonic/);
|
||||
expect(() => pair.vrfSign(MESSAGE)).toThrow(/VRF signing is not available/);
|
||||
expect(() => pair.vrfVerify(MESSAGE, new Uint8Array(96), pair.publicKey)).toThrow(/VRF verification is not available/);
|
||||
});
|
||||
});
|
||||
+68
-102
@@ -4,8 +4,8 @@
|
||||
import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@polkadot/util-crypto/types';
|
||||
import type { KeyringInstance, KeyringOptions, KeyringPair, KeyringPair$Json, KeyringPair$Meta } from './types.js';
|
||||
|
||||
import { hexToU8a, isHex, stringToU8a } from '@polkadot/util';
|
||||
import { base64Decode, decodeAddress, dilithiumPairFromMnemonic, dilithiumPairFromSeed, dilithiumPathFromSuri, ed25519PairFromSeed as ed25519FromSeed, encodeAddress, ethereumEncode, hdEthereum, isDilithium, keyExtractSuri, keyFromPath, mnemonicToLegacySeed, mnemonicToMiniSecret, secp256k1PairFromSeed as secp256k1FromSeed, sr25519PairFromSeed as sr25519FromSeed } from '@polkadot/util-crypto';
|
||||
import { hexToU8a, isHex } from '@polkadot/util';
|
||||
import { base64Decode, decodeAddress, dilithiumPairFromMnemonic, dilithiumPairFromSeed, dilithiumPathFromSuri, encodeAddress, keyExtractSuri } from '@polkadot/util-crypto';
|
||||
|
||||
import { createPair } from './pair/index.js';
|
||||
import { DEV_PHRASE } from './defaults.js';
|
||||
@@ -19,16 +19,25 @@ import { Pairs } from './pairs.js';
|
||||
* the next change to the union has one place to look, and so the error message
|
||||
* cannot drift from the check.
|
||||
*/
|
||||
const KEYPAIR_TYPES: KeypairType[] = ['dilithium65', 'dilithium87', 'ecdsa', 'ed25519', 'ethereum', 'sr25519'];
|
||||
const KEYPAIR_TYPES: KeypairType[] = ['dilithium65', 'dilithium87'];
|
||||
|
||||
const PairFromSeed = {
|
||||
dilithium65: (seed: Uint8Array): Keypair => dilithiumPairFromSeed(seed, 'dilithium65'),
|
||||
dilithium87: (seed: Uint8Array): Keypair => dilithiumPairFromSeed(seed, 'dilithium87'),
|
||||
ecdsa: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
|
||||
ed25519: (seed: Uint8Array): Keypair => ed25519FromSeed(seed),
|
||||
ethereum: (seed: Uint8Array): Keypair => secp256k1FromSeed(seed),
|
||||
sr25519: (seed: Uint8Array): Keypair => sr25519FromSeed(seed)
|
||||
};
|
||||
/** Upstream's types, refused by name so the refusal can say why. */
|
||||
const QUANTUM_UNSAFE = ['ecdsa', 'ed25519', 'ethereum', 'sr25519'];
|
||||
|
||||
/**
|
||||
* Refuse anything that is not a post-quantum keypair type.
|
||||
*
|
||||
* A key of upstream's types arriving here (a JSON backup from polkadot{.js}, a
|
||||
* caller asking for sr25519) is not a bug in this software, and "unknown crypto
|
||||
* type" would read as one. Say what it is instead. quantus/common#6
|
||||
*/
|
||||
function assertKeypairType (type: string | undefined): asserts type is KeypairType {
|
||||
if (type && QUANTUM_UNSAFE.includes(type)) {
|
||||
throw new Error(`${type} keys are not quantum-safe and cannot be held here; only ML-DSA (dilithium65, dilithium87) keys are supported`);
|
||||
} else if (!KEYPAIR_TYPES.includes(type as KeypairType)) {
|
||||
throw new Error(`Expected a keypair type of one of ${KEYPAIR_TYPES.map((t) => `'${t}'`).join(', ')}, found '${type || 'unknown'}'`);
|
||||
}
|
||||
}
|
||||
|
||||
function pairToPublic ({ publicKey }: KeyringPair): Uint8Array {
|
||||
return publicKey;
|
||||
@@ -60,14 +69,12 @@ export class Keyring implements KeyringInstance {
|
||||
public decodeAddress = decodeAddress;
|
||||
|
||||
constructor (options: KeyringOptions = {}) {
|
||||
options.type = options.type || 'ed25519';
|
||||
// ML-DSA-65 is the scheme new Quantus accounts use.
|
||||
options.type = options.type || 'dilithium65';
|
||||
|
||||
// A runtime whitelist behind the KeypairType union, so widening the type
|
||||
// alone was not enough — the compiler cannot see this one, and it fails at
|
||||
// construction rather than at use.
|
||||
if (!KEYPAIR_TYPES.includes(options.type || ('undefined' as KeypairType))) {
|
||||
throw new Error(`Expected a keyring type of one of ${KEYPAIR_TYPES.map((t) => `'${t}'`).join(', ')}, found '${options.type || 'unknown'}'`);
|
||||
}
|
||||
// Behind the union type: a JavaScript caller, or one casting, can still pass
|
||||
// anything, and this fails at construction rather than at use.
|
||||
assertKeypairType(options.type);
|
||||
|
||||
this.#pairs = new Pairs();
|
||||
this.#ss58 = options.ss58Format;
|
||||
@@ -89,7 +96,7 @@ export class Keyring implements KeyringInstance {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns the type of the keyring, ed25519, sr25519 or ecdsa
|
||||
* @description Returns the type of the keyring: dilithium65 or dilithium87
|
||||
*/
|
||||
public get type (): KeypairType {
|
||||
return this.#type;
|
||||
@@ -112,9 +119,14 @@ export class Keyring implements KeyringInstance {
|
||||
* `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.
|
||||
*/
|
||||
public addFromAddress (address: string | Uint8Array, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, type: KeypairType = this.type, ignoreChecksum?: boolean, encType?: EncryptedJsonEncoding[]): KeyringPair {
|
||||
const publicKey = this.decodeAddress(address, ignoreChecksum);
|
||||
assertKeypairType(type);
|
||||
|
||||
return this.addPair(createPair({ toSS58: this.encodeAddress, type }, { publicKey, secretKey: new Uint8Array() }, meta, encoded, encType));
|
||||
// An address is a Poseidon2 hash of an ML-DSA public key, not the key, so it
|
||||
// is carried as an account id. Upstream passed it as `publicKey`, which for
|
||||
// these types made every watch-only account report the hash of its address.
|
||||
const accountId = this.decodeAddress(address, ignoreChecksum);
|
||||
|
||||
return this.addPair(createPair({ toSS58: this.encodeAddress, type }, { accountId, publicKey: new Uint8Array(), secretKey: new Uint8Array() }, meta, encoded, encType));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,7 +171,7 @@ export class Keyring implements KeyringInstance {
|
||||
*/
|
||||
public addFromSeed (seed: Uint8Array, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||
return this.addPair(
|
||||
createPair({ toSS58: this.encodeAddress, type }, PairFromSeed[type](seed), meta, null)
|
||||
this.createFromPair(dilithiumPairFromSeed(seed, type), meta, type)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,17 +202,11 @@ export class Keyring implements KeyringInstance {
|
||||
? [type]
|
||||
: type;
|
||||
|
||||
// Same list, second copy — this one guards JSON coming in from a file the
|
||||
// user chose, so it is the guard that will matter most once quantus/common#6
|
||||
// narrows the union and imports of quantum-unsafe keys have to be refused
|
||||
// with a message explaining why rather than "unknown crypto type".
|
||||
if (!KEYPAIR_TYPES.includes(cryptoType as KeypairType)) {
|
||||
throw new Error(`Unknown crypto type ${cryptoType}`);
|
||||
}
|
||||
// The guard that matters most: JSON arrives from a file the user chose, and a
|
||||
// polkadot{.js} backup of an sr25519 account must be refused with a reason,
|
||||
// not constructed and not reported as an unknown type.
|
||||
assertKeypairType(cryptoType);
|
||||
|
||||
// For the curve schemes the address and publicKey are 32 bytes and
|
||||
// isomorphic, which is why the address field holds the public key for
|
||||
// ethereum pairs and why this works at all.
|
||||
const raw = isHex(address)
|
||||
? hexToU8a(address)
|
||||
: this.decodeAddress(address, ignoreChecksum);
|
||||
@@ -208,17 +214,12 @@ export class Keyring implements KeyringInstance {
|
||||
? hexToU8a(encoded)
|
||||
: base64Decode(encoded);
|
||||
|
||||
// ML-DSA breaks the isomorphism: the account id is a one-way Poseidon2 hash
|
||||
// and the 1952/2592-byte public key is inside `decoded`, which is encrypted
|
||||
// and stays that way until someone supplies a password. So pass the account
|
||||
// id as an account id rather than pretending it is a key — createPair uses
|
||||
// it for the address while locked, and checks it against the real public key
|
||||
// the moment `decodePkcs8` produces one.
|
||||
const info = isDilithium(cryptoType as KeypairType)
|
||||
? { accountId: raw, publicKey: new Uint8Array(), secretKey: new Uint8Array() }
|
||||
: { publicKey: raw, secretKey: new Uint8Array() };
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type: cryptoType as KeypairType }, info, meta, decoded, encType);
|
||||
// The account id is a one-way Poseidon2 hash, and the 1952/2592-byte public
|
||||
// key is inside `decoded`, encrypted until someone supplies a password. So
|
||||
// carry the account id as an account id: createPair uses it for the address
|
||||
// while locked, and checks it against the real public key the moment
|
||||
// `decodePkcs8` produces one.
|
||||
return createPair({ toSS58: this.encodeAddress, type: cryptoType }, { accountId: raw, publicKey: new Uint8Array(), secretKey: new Uint8Array() }, meta, decoded, encType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -226,6 +227,8 @@ export class Keyring implements KeyringInstance {
|
||||
* @summary Creates a pair from an explicit publicKey/secreteKey combination
|
||||
*/
|
||||
public createFromPair (pair: Keypair, meta: KeyringPair$Meta = {}, type: KeypairType = this.type): KeyringPair {
|
||||
assertKeypairType(type);
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type }, pair, meta, null);
|
||||
}
|
||||
|
||||
@@ -234,77 +237,40 @@ export class Keyring implements KeyringInstance {
|
||||
* @summary Creates a Keypair from an suri
|
||||
* @description This creates a pair from the suri, but does not add it to the keyring
|
||||
*/
|
||||
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, wordlist?: string[]): KeyringPair {
|
||||
// here we only aut-add the dev phrase if we have a hard-derived path
|
||||
public createFromUri (_suri: string, meta: KeyringPair$Meta = {}, type: KeypairType = this.type, _wordlist?: string[]): KeyringPair {
|
||||
assertKeypairType(type);
|
||||
|
||||
// here we only auto-add the dev phrase if we have a hard-derived path
|
||||
const suri = _suri.startsWith('//')
|
||||
? `${DEV_PHRASE}${_suri}`
|
||||
: _suri;
|
||||
const { derivePath, password, path, phrase } = keyExtractSuri(suri);
|
||||
let seed: Uint8Array;
|
||||
const isPhraseHex = isHex(phrase, 256);
|
||||
const { derivePath, password, phrase } = keyExtractSuri(suri);
|
||||
|
||||
// ML-DSA derives from the mnemonic itself along a hardened BIP44 path, not
|
||||
// from a seed along a junction chain — lattice keys have no public
|
||||
// derivability, so there is no soft junction to emulate and the chain's own
|
||||
// Pair::derive refuses for the same reason. Handled before the seeding below
|
||||
// because that seeding (mnemonicToMiniSecret) is the wrong one for us and
|
||||
// would produce a valid key for an account nobody owns.
|
||||
if (isDilithium(type)) {
|
||||
if (isPhraseHex) {
|
||||
// A raw 32-byte seed goes straight into keygen, which is how the
|
||||
// dev-genesis accounts are defined. Combining one with a derivation path
|
||||
// is ambiguous — is the seed the master, or already derived? — so refuse
|
||||
// rather than pick.
|
||||
if (derivePath) {
|
||||
throw new Error('A derivation path cannot be combined with a raw seed for post-quantum pairs');
|
||||
}
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type }, PairFromSeed[type](hexToU8a(phrase)), meta, null);
|
||||
// from a seed along a junction chain: lattice keys have no public
|
||||
// derivability, so there is no soft junction to emulate, and the chain's own
|
||||
// Pair::derive refuses for the same reason.
|
||||
if (isHex(phrase, 256)) {
|
||||
// A raw 32-byte seed goes straight into keygen, which is how the
|
||||
// dev-genesis accounts are defined. Combining one with a derivation path
|
||||
// is ambiguous (is the seed the master, or already derived?), so refuse
|
||||
// rather than pick.
|
||||
if (derivePath) {
|
||||
throw new Error('A derivation path cannot be combined with a raw seed for post-quantum pairs');
|
||||
}
|
||||
|
||||
return createPair(
|
||||
{ toSS58: this.encodeAddress, type },
|
||||
dilithiumPairFromMnemonic(phrase, password || '', dilithiumPathFromSuri(type, derivePath), type),
|
||||
meta,
|
||||
null
|
||||
);
|
||||
return this.createFromPair(dilithiumPairFromSeed(hexToU8a(phrase), type), meta, type);
|
||||
}
|
||||
|
||||
if (isPhraseHex) {
|
||||
seed = hexToU8a(phrase);
|
||||
} else {
|
||||
const parts = phrase.split(' ');
|
||||
|
||||
if ([12, 15, 18, 21, 24].includes(parts.length)) {
|
||||
seed = type === 'ethereum'
|
||||
? mnemonicToLegacySeed(phrase, '', false, 64)
|
||||
: mnemonicToMiniSecret(phrase, password, wordlist);
|
||||
} else {
|
||||
if (phrase.length > 32) {
|
||||
throw new Error('specified phrase is not a valid mnemonic and is invalid as a raw seed at > 32 bytes');
|
||||
}
|
||||
|
||||
seed = stringToU8a(phrase.padEnd(32));
|
||||
}
|
||||
}
|
||||
|
||||
const derived = type === 'ethereum'
|
||||
? isPhraseHex
|
||||
? PairFromSeed[type](seed) // for eth, if the private key is provided as suri, it must be derived only once
|
||||
: hdEthereum(seed, derivePath.substring(1))
|
||||
: keyFromPath(PairFromSeed[type](seed), path, type);
|
||||
|
||||
return createPair({ toSS58: this.encodeAddress, type }, derived, meta, null);
|
||||
return this.createFromPair(
|
||||
dilithiumPairFromMnemonic(phrase, password || '', dilithiumPathFromSuri(type, derivePath), type),
|
||||
meta,
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name encodeAddress
|
||||
* @description Encodes the input into an ss58 representation
|
||||
*/
|
||||
public encodeAddress = (address: Uint8Array | string, ss58Format?: number): string => {
|
||||
return this.type === 'ethereum'
|
||||
? ethereumEncode(address)
|
||||
: encodeAddress(address, ss58Format ?? this.#ss58);
|
||||
return encodeAddress(address, ss58Format ?? this.#ss58);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/keyring', path: 'auto', type: 'auto', version: '14.0.3-quantus.2' };
|
||||
export const packageInfo = { name: '@polkadot/keyring', path: 'auto', type: 'auto', version: '14.0.3-quantus.3' };
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
describe('decode', (): void => {
|
||||
it('fails when no data provided', (): void => {
|
||||
expect(
|
||||
(): void => keyring.alice.decodePkcs8()
|
||||
).toThrow(/No encrypted data available/);
|
||||
});
|
||||
|
||||
it('returns correct publicKey from encoded', (): void => {
|
||||
const PASS = 'testing';
|
||||
|
||||
expect(
|
||||
(): void => keyring.alice.decodePkcs8(
|
||||
PASS, keyring.alice.encodePkcs8(PASS)
|
||||
)
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -88,13 +88,4 @@ describe('dilithium pairs', (): void => {
|
||||
it('refuses to VRF sign', (): void => {
|
||||
expect(() => alice.vrfSign(MESSAGE)).toThrow(/not available for dilithium87/);
|
||||
});
|
||||
|
||||
// The classical paths must be untouched — this is what a careless rebase
|
||||
// breaks, and it would break silently.
|
||||
it('leaves sr25519 unchanged', (): void => {
|
||||
const sr = new Keyring({ type: 'sr25519' }).addFromUri('//Alice');
|
||||
|
||||
expect(sr.address).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
expect(sr.sign(MESSAGE).length).toEqual(64);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -94,11 +94,4 @@ describe('dilithium derivation', (): void => {
|
||||
|
||||
expect(() => pair.derive('//1')).toThrow(/derive from the mnemonic with createFromUri/);
|
||||
});
|
||||
|
||||
it('leaves sr25519 junction derivation unchanged', (): void => {
|
||||
const sr = new Keyring({ type: 'sr25519' });
|
||||
|
||||
expect(sr.createFromUri('//Alice').address).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
expect(sr.createFromUri('//Bob').address).toEqual('5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,15 +100,4 @@ describe('dilithium account JSON', (): void => {
|
||||
expect(restored.address).toEqual(other.address);
|
||||
expect(() => restored.decodePkcs8(PASSWORD)).toThrow(/does not match the address/);
|
||||
});
|
||||
|
||||
it('leaves sr25519 JSON round-tripping unchanged', (): void => {
|
||||
const sr = new Keyring({ type: 'sr25519' });
|
||||
const pair = sr.addFromUri('//Alice');
|
||||
const restored = sr.createFromJson(pair.toJson(PASSWORD));
|
||||
|
||||
restored.decodePkcs8(PASSWORD);
|
||||
|
||||
expect(restored.address).toEqual(pair.address);
|
||||
expect(restored.publicKey).toEqual(pair.publicKey);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { NONCE_LENGTH, SCRYPT_LENGTH } from '@polkadot/util-crypto/json/constants';
|
||||
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
import { PAIR_DIV, PAIR_HDR, PUB_LENGTH, SEC_LENGTH } from './defaults.js';
|
||||
|
||||
const DECODED_LENGTH = PAIR_DIV.length + PAIR_HDR.length + PUB_LENGTH + SEC_LENGTH;
|
||||
const ENCODED_LENGTH = 16 + DECODED_LENGTH + NONCE_LENGTH + SCRYPT_LENGTH;
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
describe('encode', (): void => {
|
||||
it('returns PKCS8 when no passphrase supplied', (): void => {
|
||||
expect(
|
||||
keyring.alice.encodePkcs8()
|
||||
).toHaveLength(DECODED_LENGTH);
|
||||
});
|
||||
|
||||
it('returns encoded PKCS8 when passphrase supplied', (): void => {
|
||||
expect(
|
||||
keyring.alice.encodePkcs8('testing')
|
||||
).toHaveLength(ENCODED_LENGTH);
|
||||
});
|
||||
});
|
||||
@@ -1,189 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a, u8aToHex } from '@polkadot/util';
|
||||
import { cryptoWaitReady, encodeAddress as toSS58, setSS58Format } from '@polkadot/util-crypto';
|
||||
|
||||
import { PAIRSSR25519 } from '../testing.js';
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
import { createPair } from './index.js';
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
const TEST_ADDRESS = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('pair', (): void => {
|
||||
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 => {
|
||||
expect(
|
||||
keyring.alice.publicKey
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
expect(
|
||||
keyring.alice.addressRaw
|
||||
).toEqual(
|
||||
new Uint8Array([209, 114, 167, 76, 218, 76, 134, 89, 18, 195, 43, 160, 168, 10, 87, 174, 105, 171, 174, 65, 14, 92, 203, 89, 222, 232, 78, 47, 68, 50, 219, 79])
|
||||
);
|
||||
});
|
||||
|
||||
it('allows signing', (): void => {
|
||||
expect(
|
||||
keyring.alice.sign(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64])
|
||||
)
|
||||
).toEqual(SIGNATURE);
|
||||
});
|
||||
|
||||
it('validates a correctly signed message', (): void => {
|
||||
expect(
|
||||
keyring.alice.verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
SIGNATURE,
|
||||
keyring.alice.publicKey
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('fails a correctly signed message (signer changed)', (): void => {
|
||||
expect(
|
||||
keyring.alice.verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
SIGNATURE,
|
||||
keyring.bob.publicKey
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('fails a correctly signed message (message changed)', (): void => {
|
||||
expect(
|
||||
keyring.alice.verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]),
|
||||
SIGNATURE,
|
||||
keyring.alice.publicKey
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('allows vrf sign and verify', (): void => {
|
||||
const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]);
|
||||
|
||||
expect(
|
||||
keyring.alice.vrfVerify(
|
||||
message,
|
||||
keyring.alice.vrfSign(message),
|
||||
keyring.alice.publicKey
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('fails vrf sign and verify (publicKey changed)', (): void => {
|
||||
const message = new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]);
|
||||
|
||||
expect(
|
||||
keyring.alice.vrfVerify(
|
||||
message,
|
||||
keyring.alice.vrfSign(message),
|
||||
keyring.bob.publicKey
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('allows setting/getting of meta', (): void => {
|
||||
keyring.bob.setMeta({ foo: 'bar', something: 'else' });
|
||||
|
||||
expect(keyring.bob.meta).toMatchObject({ foo: 'bar', something: 'else' });
|
||||
|
||||
keyring.bob.setMeta({ something: 'thing' });
|
||||
|
||||
expect(keyring.bob.meta).toMatchObject({ foo: 'bar', something: 'thing' });
|
||||
});
|
||||
|
||||
it('allows encoding of address with different prefixes', (): void => {
|
||||
expect(keyring.alice.address).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');
|
||||
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
setSS58Format(255);
|
||||
|
||||
expect(keyring.alice.address).toEqual('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k');
|
||||
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
setSS58Format(42);
|
||||
});
|
||||
|
||||
it('allows getting public key after decoding', (): void => {
|
||||
const PASS = 'testing';
|
||||
const encoded = keyring.alice.encodePkcs8(PASS);
|
||||
|
||||
const pair = createPair({ toSS58, type: 'sr25519' }, { publicKey: keyring.alice.publicKey });
|
||||
|
||||
pair.decodePkcs8(PASS, encoded);
|
||||
|
||||
expect(pair.isLocked).toEqual(false);
|
||||
});
|
||||
|
||||
it('allows derivation on the pair', (): void => {
|
||||
const alice = createPair({ toSS58, type: 'sr25519' }, { publicKey: hexToU8a(PAIRSSR25519[0].p), secretKey: hexToU8a(PAIRSSR25519[0].s) }, {});
|
||||
const stash = alice.derive('//stash');
|
||||
const soft = alice.derive('//funding/0');
|
||||
|
||||
expect(stash.publicKey).toEqual(hexToU8a(PAIRSSR25519[1].p));
|
||||
expect(soft.address).toEqual('5ECQNn7UueWHPFda5qUi4fTmTtyCnPvGnuoyVVSj5CboJh9J');
|
||||
});
|
||||
|
||||
it('fails to sign when locked', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'sr25519' }, { publicKey: keyring.alice.publicKey });
|
||||
|
||||
expect(pair.isLocked).toEqual(true);
|
||||
expect((): Uint8Array =>
|
||||
pair.sign(new Uint8Array([0]))
|
||||
).toThrow('Cannot sign with a locked key pair');
|
||||
});
|
||||
|
||||
describe('ethereum', (): void => {
|
||||
const PUBLICDERIVED = new Uint8Array([
|
||||
3, 129, 53, 27, 27, 70, 210, 96,
|
||||
43, 9, 146, 187, 93, 85, 49, 249,
|
||||
193, 105, 107, 8, 18, 254, 178, 83,
|
||||
75, 104, 132, 173, 196, 126, 46, 29,
|
||||
139
|
||||
]);
|
||||
const SECRETDERIVED = new Uint8Array([
|
||||
7, 13, 195, 17, 115, 0, 1, 25,
|
||||
24, 226, 107, 2, 23, 105, 69, 204,
|
||||
21, 195, 213, 72, 207, 73, 253, 132,
|
||||
24, 217, 127, 147, 175, 105, 158, 70
|
||||
]);
|
||||
|
||||
it('has a valid address from a known public', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077') });
|
||||
|
||||
expect(pair.address).toEqual(TEST_ADDRESS);
|
||||
expect(pair.addressRaw).toEqual(hexToU8a(TEST_ADDRESS));
|
||||
});
|
||||
|
||||
it('has a valid address from a known ethereum address (20 length)', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: new Uint8Array([75, 32, 205, 127, 248, 119, 52, 31, 46, 171, 170, 23, 158, 23, 46, 108, 95, 180, 186, 168]), secretKey: new Uint8Array([]) });
|
||||
|
||||
expect(pair.address.toLowerCase()).toEqual('0x4b20cd7ff877341f2eabaa179e172e6c5fb4baa8');
|
||||
expect(pair.addressRaw).toEqual(hexToU8a('0x4b20cd7ff877341f2eabaa179e172e6c5fb4baa8'));
|
||||
});
|
||||
|
||||
it('converts to json', (): void => {
|
||||
const pair = createPair({ toSS58, type: 'ethereum' }, { publicKey: PUBLICDERIVED, secretKey: SECRETDERIVED });
|
||||
const json = pair.toJson('password');
|
||||
|
||||
expect(json.encoding).toEqual({
|
||||
content: ['pkcs8', 'ethereum'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
});
|
||||
expect(json.address).toEqual(u8aToHex(PUBLICDERIVED));
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { EncryptedJsonEncoding, Keypair, KeypairType } from '@polkadot/util-crypto/types';
|
||||
import type { EncryptedJsonEncoding, KeypairType } from '@polkadot/util-crypto/types';
|
||||
import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta, SignOptions } from '../types.js';
|
||||
import type { PairInfo } from './types.js';
|
||||
|
||||
import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
import { blake2AsU8a, dilithiumAccountFromPublic, dilithiumPairFromSeed, dilithiumSign, dilithiumSizes, ed25519PairFromSeed as ed25519FromSeed, ed25519Sign, ethereumEncode, isDilithium, keccakAsU8a, keyExtractPath, keyFromPath, secp256k1Compress, secp256k1Expand, secp256k1PairFromSeed as secp256k1FromSeed, secp256k1Sign, signatureVerify, sr25519PairFromSeed as sr25519FromSeed, sr25519Sign, sr25519VrfSign, sr25519VrfVerify } from '@polkadot/util-crypto';
|
||||
import { objectSpread, u8aConcat, u8aEmpty, u8aEq, u8aToU8a } from '@polkadot/util';
|
||||
import { dilithiumAccountFromPublic, dilithiumSign, dilithiumSizes, dilithiumVerify } from '@polkadot/util-crypto';
|
||||
|
||||
import { decodePair } from './decode.js';
|
||||
import { encodePair } from './encode.js';
|
||||
@@ -19,44 +19,17 @@ interface Setup {
|
||||
|
||||
const SIG_TYPE_NONE = new Uint8Array();
|
||||
|
||||
const TYPE_FROM_SEED = {
|
||||
dilithium65: (seed: Uint8Array) => dilithiumPairFromSeed(seed, 'dilithium65'),
|
||||
dilithium87: (seed: Uint8Array) => dilithiumPairFromSeed(seed, 'dilithium87'),
|
||||
ecdsa: secp256k1FromSeed,
|
||||
ed25519: ed25519FromSeed,
|
||||
ethereum: secp256k1FromSeed,
|
||||
sr25519: sr25519FromSeed
|
||||
};
|
||||
|
||||
// For the curve types these index Substrate's `MultiSignature`. For the ML-DSA
|
||||
// types they index the Quantus runtime's `DilithiumSignatureScheme`, where
|
||||
// `Dilithium87` is variant 0 and `Dilithium65` is variant 1 — a different enum
|
||||
// that happens to be reached by the same `withType` mechanism, so no special
|
||||
// casing is needed anywhere upstream of here.
|
||||
const TYPE_PREFIX = {
|
||||
// The Quantus runtime's `DilithiumSignatureScheme`: `Dilithium87` is variant 0
|
||||
// and `Dilithium65` variant 1. Reached through the same `withType` mechanism
|
||||
// upstream used for Substrate's `MultiSignature`, so nothing above this file
|
||||
// needs to know which enum it is.
|
||||
//
|
||||
// There are no other arms. Upstream's ed25519, sr25519, ecdsa and ethereum were
|
||||
// removed with their primitives: each falls to Shor's algorithm, and this keyring
|
||||
// exists to hold keys that do not. quantus/common#6
|
||||
const TYPE_PREFIX: Record<KeypairType, Uint8Array> = {
|
||||
dilithium65: new Uint8Array([1]),
|
||||
dilithium87: new Uint8Array([0]),
|
||||
ecdsa: new Uint8Array([2]),
|
||||
ed25519: new Uint8Array([0]),
|
||||
ethereum: new Uint8Array([2]),
|
||||
sr25519: new Uint8Array([1])
|
||||
};
|
||||
|
||||
// The ML-DSA arms take a context; the curve arms ignore the extra argument. The
|
||||
// signature is written out because TypeScript would otherwise infer the narrowest
|
||||
// common shape — the two-argument curve arms — and reject passing a context at all.
|
||||
type SignFn = (message: Uint8Array, pair: Partial<Keypair>, context?: Uint8Array) => Uint8Array;
|
||||
|
||||
const TYPE_SIGNATURE: Record<KeypairType, SignFn> = {
|
||||
dilithium65: (m, p, ctx) => dilithiumSign(m, p, 'dilithium65', requireContext(ctx)),
|
||||
dilithium87: (m, p, ctx) => dilithiumSign(m, p, 'dilithium87', requireContext(ctx)),
|
||||
ecdsa: (m, p) => secp256k1Sign(m, p, 'blake2'),
|
||||
// Wrapped rather than passed directly: ed25519Sign and sr25519Sign take a
|
||||
// third argument of their own (`onlyJs`), and letting it line up with the
|
||||
// context slot would silently reinterpret a Uint8Array as a boolean.
|
||||
ed25519: (m, p) => ed25519Sign(m, p),
|
||||
ethereum: (m, p) => secp256k1Sign(m, p, 'keccak'),
|
||||
sr25519: (m, p) => sr25519Sign(m, p)
|
||||
dilithium87: new Uint8Array([0])
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -74,30 +47,10 @@ function requireContext (context?: Uint8Array): Uint8Array {
|
||||
return context;
|
||||
}
|
||||
|
||||
// Every curve arm here is identity or a cheap re-encoding, because on Substrate
|
||||
// the address *is* the public key. The ML-DSA arms are the exception that the
|
||||
// rest of this file has to be read in light of: 1952 or 2592 bytes in, 32 out,
|
||||
// and no way back. `ecdsa` already hints at the shape — it hashes when given an
|
||||
// uncompressed key — but it stays reversible in the sense that matters, since
|
||||
// the signature carries a recoverable key. ML-DSA does not, which is why its
|
||||
// signatures carry the public key explicitly.
|
||||
const TYPE_ADDRESS = {
|
||||
dilithium65: dilithiumAccountFromPublic,
|
||||
dilithium87: dilithiumAccountFromPublic,
|
||||
ecdsa: (p: Uint8Array) => p.length > 32 ? blake2AsU8a(p) : p,
|
||||
ed25519: (p: Uint8Array) => p,
|
||||
ethereum: (p: Uint8Array) => p.length === 20 ? p : keccakAsU8a(secp256k1Expand(p)),
|
||||
sr25519: (p: Uint8Array) => p
|
||||
};
|
||||
|
||||
function isLocked (secretKey?: Uint8Array): secretKey is undefined {
|
||||
return !secretKey || u8aEmpty(secretKey);
|
||||
}
|
||||
|
||||
function vrfHash (proof: Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): Uint8Array {
|
||||
return blake2AsU8a(u8aConcat(context || '', extra || '', proof));
|
||||
}
|
||||
|
||||
/**
|
||||
* @name createPair
|
||||
* @summary Creates a keyring pair object
|
||||
@@ -132,40 +85,22 @@ function vrfHash (proof: Uint8Array, context?: string | Uint8Array, extra?: stri
|
||||
export function createPair ({ toSS58, type }: Setup, { accountId, publicKey, secretKey }: PairInfo, meta: KeyringPair$Meta = {}, encoded: Uint8Array | null = null, encTypes?: EncryptedJsonEncoding[]): KeyringPair {
|
||||
const decodePkcs8 = (passphrase?: string, userEncoded?: Uint8Array | null): void => {
|
||||
// ML-DSA secrets are 4032/4896 bytes, which is neither of the two lengths
|
||||
// decodePair would otherwise try. It has to be told.
|
||||
const decoded = decodePair(passphrase, userEncoded || encoded, encTypes, isDilithium(type)
|
||||
? dilithiumSizes(type).secretKey
|
||||
: undefined
|
||||
);
|
||||
// decodePair would otherwise try, so it has to be told.
|
||||
const decoded = decodePair(passphrase, userEncoded || encoded, encTypes, dilithiumSizes(type).secretKey);
|
||||
|
||||
// Upstream decides "is this a secret key or a seed?" by length, because for
|
||||
// the curve schemes a 64-byte blob is a secret and a 32-byte one is a seed.
|
||||
// An ML-DSA secret is neither, and would take the seed branch and be fed to
|
||||
// keygen as entropy — producing a valid, wrong key in silence. The type
|
||||
// knows the answer, so ask it.
|
||||
if (isDilithium(type) || decoded.secretKey.length === 64) {
|
||||
publicKey = decoded.publicKey;
|
||||
secretKey = decoded.secretKey;
|
||||
} else {
|
||||
const pair = TYPE_FROM_SEED[type](decoded.secretKey);
|
||||
|
||||
publicKey = pair.publicKey;
|
||||
secretKey = pair.secretKey;
|
||||
}
|
||||
publicKey = decoded.publicKey;
|
||||
secretKey = decoded.secretKey;
|
||||
|
||||
// The public key has only now arrived. If the pair was constructed from JSON
|
||||
// it has been reporting an address carried as data since then, so check the
|
||||
// two agree.
|
||||
//
|
||||
// This is not a paranoid check. For every other scheme the address *is* the
|
||||
// public key, so a JSON file whose `address` field has been edited cannot
|
||||
// decode at all. Here it decodes perfectly and yields a pair that reports an
|
||||
// address its key does not control — a user would see the attacker's address
|
||||
// in their own wallet and believe they held it.
|
||||
// This is not a paranoid check. A JSON file whose `address` field has been
|
||||
// edited decodes perfectly and would yield a pair reporting an address its
|
||||
// key does not control: a user would see an attacker's address in their own
|
||||
// wallet and believe they held it.
|
||||
if (accountId) {
|
||||
const derived = TYPE_ADDRESS[type](publicKey);
|
||||
|
||||
if (!u8aEq(derived, accountId)) {
|
||||
if (!u8aEq(dilithiumAccountFromPublic(publicKey), accountId)) {
|
||||
throw new Error('Decoded public key does not match the address in the account JSON');
|
||||
}
|
||||
|
||||
@@ -182,31 +117,19 @@ export function createPair ({ toSS58, type }: Setup, { accountId, publicKey, sec
|
||||
return encoded;
|
||||
};
|
||||
|
||||
// While an ML-DSA pair restored from JSON is locked, its public key is still
|
||||
// inside the encrypted blob and the account id cannot be computed — so use the
|
||||
// one carried alongside. `decodePkcs8` clears it once the real key arrives and
|
||||
// has been checked against it.
|
||||
// While a pair restored from JSON, or added by address, has no public key,
|
||||
// its account id cannot be computed (it is a one-way Poseidon2 hash of that
|
||||
// key), so use the one carried alongside. `decodePkcs8` clears it once the
|
||||
// real key arrives and has been checked against it.
|
||||
const addressRawOf = (): Uint8Array =>
|
||||
accountId ?? TYPE_ADDRESS[type](publicKey);
|
||||
|
||||
const encodeAddress = (): string => {
|
||||
const raw = addressRawOf();
|
||||
|
||||
return type === 'ethereum'
|
||||
? ethereumEncode(raw)
|
||||
: toSS58(raw);
|
||||
};
|
||||
accountId ?? dilithiumAccountFromPublic(publicKey);
|
||||
|
||||
return {
|
||||
get address (): string {
|
||||
return encodeAddress();
|
||||
return toSS58(addressRawOf());
|
||||
},
|
||||
get addressRaw (): Uint8Array {
|
||||
const raw = addressRawOf();
|
||||
|
||||
return type === 'ethereum'
|
||||
? raw.slice(-20)
|
||||
: raw;
|
||||
return addressRawOf();
|
||||
},
|
||||
get isLocked (): boolean {
|
||||
return isLocked(secretKey);
|
||||
@@ -222,24 +145,12 @@ export function createPair ({ toSS58, type }: Setup, { accountId, publicKey, sec
|
||||
},
|
||||
// eslint-disable-next-line sort-keys
|
||||
decodePkcs8,
|
||||
derive: (suri: string, meta?: KeyringPair$Meta): KeyringPair => {
|
||||
if (type === 'ethereum') {
|
||||
throw new Error('Unable to derive on this keypair');
|
||||
} else if (isDilithium(type)) {
|
||||
// Not "not implemented" — not possible. A child here would have to come
|
||||
// from this pair's key material, and ML-DSA keys are not derivable from
|
||||
// one another at all; the Quantus tree derives every account from the
|
||||
// mnemonic independently. So the caller needs the mnemonic, not this
|
||||
// pair, and saying that is more use than a generic refusal.
|
||||
throw new Error(`Unable to derive from an existing ${type} pair; derive from the mnemonic with createFromUri instead`);
|
||||
} else if (isLocked(secretKey)) {
|
||||
throw new Error('Cannot derive on a locked keypair');
|
||||
}
|
||||
|
||||
const { path } = keyExtractPath(suri);
|
||||
const derived = keyFromPath({ publicKey, secretKey }, path, type);
|
||||
|
||||
return createPair({ toSS58, type }, derived, meta, null);
|
||||
derive: (_suri: string, _meta?: KeyringPair$Meta): KeyringPair => {
|
||||
// Not "not implemented" — not possible. A child here would have to come
|
||||
// from this pair's key material, and ML-DSA keys are not derivable from
|
||||
// one another at all; the Quantus tree derives every account from the
|
||||
// mnemonic independently. So the caller needs the mnemonic, not this pair.
|
||||
throw new Error(`Unable to derive from an existing ${type} pair; derive from the mnemonic with createFromUri instead`);
|
||||
},
|
||||
encodePkcs8: (passphrase?: string): Uint8Array => {
|
||||
return recode(passphrase);
|
||||
@@ -259,57 +170,37 @@ export function createPair ({ toSS58, type }: Setup, { accountId, publicKey, sec
|
||||
options.withType
|
||||
? TYPE_PREFIX[type]
|
||||
: SIG_TYPE_NONE,
|
||||
TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey }, options.context)
|
||||
dilithiumSign(u8aToU8a(message), { publicKey, secretKey }, type, requireContext(options.context))
|
||||
);
|
||||
},
|
||||
toJson: (passphrase?: string): KeyringPair$Json => {
|
||||
// NOTE: For ecdsa and ethereum, the publicKey cannot be extracted from the address. For these
|
||||
// pass the hex-encoded publicKey through to the address portion of the JSON (before decoding)
|
||||
// unless the publicKey is already an address
|
||||
const address = ['ecdsa', 'ethereum'].includes(type)
|
||||
? publicKey.length === 20
|
||||
? u8aToHex(publicKey)
|
||||
: u8aToHex(secp256k1Compress(publicKey))
|
||||
: encodeAddress();
|
||||
|
||||
return pairToJson(type, { address, meta }, recode(passphrase), !!passphrase);
|
||||
return pairToJson(type, { address: toSS58(addressRawOf()), meta }, recode(passphrase), !!passphrase);
|
||||
},
|
||||
unlock: (passphrase?: string): void => {
|
||||
return decodePkcs8(passphrase);
|
||||
},
|
||||
verify: (message: string | Uint8Array, signature: string | Uint8Array, signerPublic: string | Uint8Array): boolean => {
|
||||
return signatureVerify(message, signature, TYPE_ADDRESS[type](u8aToU8a(signerPublic))).isValid;
|
||||
verify: (message: string | Uint8Array, signature: string | Uint8Array, signerPublic: string | Uint8Array, context: Uint8Array = new Uint8Array()): boolean => {
|
||||
const pk = u8aToU8a(signerPublic);
|
||||
const sig = u8aToU8a(signature);
|
||||
const { signature: sigLength } = dilithiumSizes(type);
|
||||
|
||||
// Accept a bare signature or the chain's `signature ‖ publicKey` form; the
|
||||
// verifier needs the latter, and the public key is at hand either way.
|
||||
const sigWithPublic = sig.length === sigLength
|
||||
? u8aConcat(sig, pk)
|
||||
: sig;
|
||||
|
||||
// Unlike signing, a wrong context here cannot produce anything harmful —
|
||||
// only `false` — so the empty context used for raw bytes is the default.
|
||||
return dilithiumVerify(u8aToU8a(message), sigWithPublic, dilithiumAccountFromPublic(pk), type, context);
|
||||
},
|
||||
vrfSign: (message: string | Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): Uint8Array => {
|
||||
if (isLocked(secretKey)) {
|
||||
throw new Error('Cannot sign with a locked key pair');
|
||||
}
|
||||
|
||||
if (type === 'sr25519') {
|
||||
return sr25519VrfSign(message, { secretKey }, context, extra);
|
||||
}
|
||||
|
||||
// There is no ML-DSA VRF. The construction below fakes one out of an
|
||||
// ordinary signature, which is sound only because the underlying schemes
|
||||
// are deterministic and unique — properties ML-DSA's signature does not
|
||||
// have in the form this needs. Refuse rather than produce something that
|
||||
// looks like a VRF output and is not verifiable as one.
|
||||
if (isDilithium(type)) {
|
||||
throw new Error(`VRF signing is not available for ${type}`);
|
||||
}
|
||||
|
||||
const proof = TYPE_SIGNATURE[type](u8aToU8a(message), { publicKey, secretKey });
|
||||
|
||||
return u8aConcat(vrfHash(proof, context, extra), proof);
|
||||
vrfSign: (): Uint8Array => {
|
||||
// There is no ML-DSA VRF, and faking one out of an ordinary signature needs
|
||||
// uniqueness ML-DSA does not have in the form required.
|
||||
throw new Error(`VRF signing is not available for ${type}`);
|
||||
},
|
||||
vrfVerify: (message: string | Uint8Array, vrfResult: Uint8Array, signerPublic: Uint8Array | string, context?: string | Uint8Array, extra?: string | Uint8Array): boolean => {
|
||||
if (type === 'sr25519') {
|
||||
return sr25519VrfVerify(message, vrfResult, publicKey, context, extra);
|
||||
}
|
||||
|
||||
const result = signatureVerify(message, u8aConcat(TYPE_PREFIX[type], vrfResult.subarray(32)), TYPE_ADDRESS[type](u8aToU8a(signerPublic)));
|
||||
|
||||
return result.isValid && u8aEq(vrfResult.subarray(0, 32), vrfHash(vrfResult.subarray(32), context, extra));
|
||||
vrfVerify: (): boolean => {
|
||||
throw new Error(`VRF verification is not available for ${type}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const json: KeyringPair$Json = {
|
||||
address,
|
||||
encoded: '',
|
||||
encoding: {
|
||||
content: ['pkcs8', 'ed25519'],
|
||||
content: ['pkcs8', 'dilithium65'],
|
||||
type: 'none',
|
||||
version: '0'
|
||||
},
|
||||
@@ -46,7 +46,7 @@ const pair: KeyringPair = {
|
||||
new Uint8Array(64),
|
||||
toJson: (_passphrase?: string): KeyringPair$Json =>
|
||||
json,
|
||||
type: 'ed25519',
|
||||
type: 'dilithium65',
|
||||
unlock: (_passphrase?: string): void =>
|
||||
undefined,
|
||||
verify: (_message: Uint8Array, _signature: Uint8Array): boolean =>
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { createTestPairs } from '../testingPairs.js';
|
||||
|
||||
const keyring = createTestPairs({ type: 'ed25519' }, false);
|
||||
|
||||
describe('toJson', (): void => {
|
||||
it('creates an unencoded output with no passphrase', (): void => {
|
||||
expect(
|
||||
keyring.alice.toJson()
|
||||
).toMatchObject({
|
||||
address: '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua',
|
||||
encoded: 'MFMCAQEwBQYDK2VwBCIEIEFsaWNlICAgICAgICAgICAgICAgICAgICAgICAgICAg0XKnTNpMhlkSwyugqApXrmmrrkEOXMtZ3uhOL0Qy20+hIwMhANFyp0zaTIZZEsMroKgKV65pq65BDlzLWd7oTi9EMttP',
|
||||
encoding: {
|
||||
content: ['pkcs8', 'ed25519'],
|
||||
type: ['none'],
|
||||
version: '3'
|
||||
},
|
||||
meta: {
|
||||
isTesting: true,
|
||||
name: 'alice'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('creates an encoded output with passphrase', (): void => {
|
||||
const json = keyring.alice.toJson('testing');
|
||||
|
||||
expect(json.encoded).toHaveLength(268);
|
||||
expect(json).toMatchObject({
|
||||
address: '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua',
|
||||
encoding: {
|
||||
content: ['pkcs8', 'ed25519'],
|
||||
type: ['scrypt', 'xsalsa20-poly1305'],
|
||||
version: '3'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
import { cryptoWaitReady, ed25519PairFromSeed, encodeAddress as toSS58, randomAsU8a, secp256k1PairFromSeed, sr25519PairFromSeed } from '@polkadot/util-crypto';
|
||||
|
||||
import { createPair } from './index.js';
|
||||
|
||||
const MESSAGE = 'this is a test message';
|
||||
const CONTEXT = 'some context';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
const ecdsa = createPair({ toSS58, type: 'ecdsa' }, secp256k1PairFromSeed(randomAsU8a()));
|
||||
const ed25519 = createPair({ toSS58, type: 'ed25519' }, ed25519PairFromSeed(randomAsU8a()));
|
||||
const sr25519 = createPair({ toSS58, type: 'sr25519' }, sr25519PairFromSeed(randomAsU8a()));
|
||||
|
||||
describe('vrf', (): void => {
|
||||
it('has deterministic signature values for ecdsa', (): void => {
|
||||
const sig1 = ecdsa.vrfSign(MESSAGE, CONTEXT);
|
||||
const sig2 = ecdsa.vrfSign(MESSAGE, CONTEXT);
|
||||
|
||||
expect(u8aToHex(sig1)).toEqual(u8aToHex(sig2));
|
||||
expect(ecdsa.vrfVerify(MESSAGE, sig1, ecdsa.publicKey, CONTEXT)).toEqual(true);
|
||||
expect(ecdsa.vrfVerify(MESSAGE, sig2, ecdsa.publicKey, CONTEXT)).toEqual(true);
|
||||
});
|
||||
|
||||
it('has deterministic signature values for ed25519', (): void => {
|
||||
const sig1 = ed25519.vrfSign(MESSAGE, CONTEXT);
|
||||
const sig2 = ed25519.vrfSign(MESSAGE, CONTEXT);
|
||||
|
||||
expect(u8aToHex(sig1)).toEqual(u8aToHex(sig2));
|
||||
expect(ed25519.vrfVerify(MESSAGE, sig1, ed25519.publicKey, CONTEXT)).toEqual(true);
|
||||
expect(ed25519.vrfVerify(MESSAGE, sig2, ed25519.publicKey, CONTEXT)).toEqual(true);
|
||||
});
|
||||
|
||||
it('has deterministic signature values for sr25519', (): void => {
|
||||
const sig1 = sr25519.vrfSign(MESSAGE, CONTEXT);
|
||||
const sig2 = sr25519.vrfSign(MESSAGE, CONTEXT);
|
||||
|
||||
expect(u8aToHex(sig1.slice(0, 32))).toEqual(u8aToHex(sig2.slice(0, 32)));
|
||||
expect(sr25519.vrfVerify(MESSAGE, sig1, sr25519.publicKey, CONTEXT)).toEqual(true);
|
||||
expect(sr25519.vrfVerify(MESSAGE, sig2, sr25519.publicKey, CONTEXT)).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
// From https://github.com/paritytech/substrate/wiki/Secret-URI-Test-Vectors
|
||||
|
||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
||||
|
||||
import Keyring from './index.js';
|
||||
|
||||
const PHRASE = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk';
|
||||
const ETHEREUM_PHRASE = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
||||
|
||||
const TESTS = {
|
||||
ecdsa: [
|
||||
{
|
||||
pk: '0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1',
|
||||
ss: '5C7C2Z5sWbytvHpuLTvzKunnnRwQxft1jiqrLD5rhucQ5S9X',
|
||||
uri: `${PHRASE}//Alice`
|
||||
}
|
||||
],
|
||||
ethereum: [
|
||||
{
|
||||
pk: '0x0381351b1b46d2602b0992bb5d5531f9c1696b0812feb2534b6884adc47e2e1d8b',
|
||||
ss: '0x31ea8795EE32D782C8ff41a5C68Dcbf0F5B27f6d',
|
||||
uri: `${ETHEREUM_PHRASE}/m/44'/60'/0'/0/0`
|
||||
},
|
||||
{
|
||||
pk: '0x02509540919faacf9ab52146c9aa40db68172d83777250b28e4679176e49ccdd9f',
|
||||
ss: '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac',
|
||||
uri: `${PHRASE}/m/44'/60'/0'/0/0`
|
||||
},
|
||||
{
|
||||
pk: '0x033bc19e36ff1673910575b6727a974a9abd80c9a875d41ab3e2648dbfb9e4b518',
|
||||
ss: '0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0',
|
||||
uri: `${PHRASE}/m/44'/60'/0'/0/1`
|
||||
}
|
||||
],
|
||||
sr25519: [
|
||||
{
|
||||
pk: '0x46ebddef8cd9bb167dc30878d7113b7e168e6f0646beffd77d69d39bad76b47a',
|
||||
ss: '5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV',
|
||||
uri: PHRASE
|
||||
},
|
||||
{
|
||||
pk: '0xb69355deefa7a8f33e9297f5af22e680f03597a99d4f4b1c44be47e7a2275802',
|
||||
ss: '5GC6LfpV352HtJPySfAecb5JdePtf4R9Vq49NUU8RhzgBqgq',
|
||||
uri: `${PHRASE}///password`
|
||||
},
|
||||
{
|
||||
pk: '0x40b9675df90efa6069ff623b0fdfcf706cd47ca7452a5056c7ad58194d23440a',
|
||||
ss: '5DXZzrDxHbkQov4QBAY4TjpwnHCMrKXkomTnKSw8UArBEY5v',
|
||||
uri: `${PHRASE}/foo`
|
||||
},
|
||||
{
|
||||
pk: '0x547d4a55642ec7ebadc0bd29b6e570b8c926059b3c0655d4948075e9a7e6f31e',
|
||||
ss: '5DyV6fZuvPemWrUqBgWwTSgoV86w6xms3KhkFU6cQcWxU8eP',
|
||||
uri: `${PHRASE}//foo`
|
||||
},
|
||||
{
|
||||
pk: '0x3841947ffcde6f5fef26fb68b59bb8665637e30e32ec2051f99cf6b9c674fe09',
|
||||
ss: '5DLU27is5iViNopQb2KxsTyPx6j4vCu8X3sk3j3NNLkPCqKM',
|
||||
uri: `${PHRASE}//foo/bar`
|
||||
},
|
||||
{
|
||||
pk: '0xdc142f7476a7b0aa262aeccf207f1d18daa90762db393006741e8a31f39dbc53',
|
||||
ss: '5H3GPTqDSpjkfDwbHy12PD6BWm8jvGSX4xYC8UMprHpTPcRg',
|
||||
uri: `${PHRASE}/foo//bar`
|
||||
},
|
||||
{
|
||||
pk: '0xa2e56b06407a6d1e819d2fc33fa0ec604b29c2e868b70b3696bb049b8725934b',
|
||||
ss: '5FkHmNgbg64MwStgCyDi2Uw3ufFu11mqQgmWT9uwK4Lghvpv',
|
||||
uri: `${PHRASE}//foo/bar//42/69`
|
||||
},
|
||||
{
|
||||
pk: '0x0e0d24e3e1ff2c07f269c99e2e0df8681fda1851ac42fc846ca2daaa90cd8f14',
|
||||
ss: '5CP8S23JBNXYNpJsL7ESPJBNnUZE6itcfM4EnDxEhaVEU6dT',
|
||||
uri: `${PHRASE}//foo/bar//42/69///password`
|
||||
},
|
||||
{
|
||||
pk: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d',
|
||||
ss: '5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
|
||||
uri: `${PHRASE}//Alice`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('keyring.addFromUri', (): void => {
|
||||
for (const [type, tests] of Object.entries(TESTS)) {
|
||||
const keyring = new Keyring({ type: type as KeypairType });
|
||||
|
||||
describe(`${type}`, (): void => {
|
||||
tests.forEach(({ pk, ss, uri }): void => {
|
||||
it(`creates ${uri}`, (): void => {
|
||||
const pair = keyring.addFromUri(uri, {}, type as KeypairType);
|
||||
|
||||
expect(u8aToHex(pair.publicKey)).toEqual(pk);
|
||||
expect(pair.address).toEqual(ss);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
+33
-136
@@ -1,151 +1,48 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@polkadot/util/types';
|
||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||
import type { KeyringInstance, KeyringOptions } from './types.js';
|
||||
|
||||
import { hexToU8a } from '@polkadot/util';
|
||||
|
||||
import { createPair } from './pair/index.js';
|
||||
import { Keyring } from './keyring.js';
|
||||
|
||||
interface PairDef {
|
||||
name?: string;
|
||||
p: HexString;
|
||||
s: HexString;
|
||||
seed?: string;
|
||||
type: KeypairType
|
||||
}
|
||||
|
||||
// NOTE This is not great since we have the secretKey here explicitly, but a testing
|
||||
// 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.
|
||||
export const PAIRSSR25519: PairDef[] = [
|
||||
{
|
||||
p: '0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d',
|
||||
s: '0x98319d4ff8a9508c4bb0cf0b5a78d760a0b2082c02775e6e82370816fedfff48925a225d97aa00682d6a59b95b18780c10d7032336e88f3442b42361f4a66011', // nosemgrep
|
||||
seed: 'Alice',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f',
|
||||
s: '0xe8da6c9d810e020f5e3c7f5af2dea314cbeaa0d72bc6421e92c0808a0c584a6046ab28e97c3ffc77fe12b5a4d37e8cd4afbfebbf2391ffc7cb07c0f38c023efd', // nosemgrep
|
||||
seed: 'Alice//stash',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48',
|
||||
s: '0x081ff694633e255136bdb456c20a5fc8fed21f8b964c11bb17ff534ce80ebd5941ae88f85d0c1bfc37be41c904e1dfc01de8c8067b0d6d5df25dd1ac0894a325', // nosemgrep
|
||||
seed: 'Bob',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e',
|
||||
s: '0xc006507cdfc267a21532394c49ca9b754ca71de21e15a1cdf807c7ceab6d0b6c3ed408d9d35311540dcd54931933e67cf1ea10d46f75408f82b789d9bd212fde', // nosemgrep
|
||||
seed: 'Bob//stash',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22',
|
||||
s: '0xa8f2d83016052e5d6d77b2f6fd5d59418922a09024cda701b3c34369ec43a7668faf12ff39cd4e5d92bb773972f41a7a5279ebc2ed92264bed8f47d344f8f18c', // nosemgrep
|
||||
seed: 'Charlie',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20',
|
||||
s: '0x20e05482ca4677e0edbc58ae9a3a59f6ed3b1a9484ba17e64d6fe8688b2b7b5d108c4487b9323b98b11fe36cb301b084e920f7b7895536809a6d62a451b25568', // nosemgrep
|
||||
seed: 'Dave',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0xe659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e',
|
||||
s: '0x683576abfd5dc35273e4264c23095a1bf21c14517bece57c7f0cc5c0ed4ce06a3dbf386b7828f348abe15d76973a72009e6ef86a5c91db2990cb36bb657c6587', // nosemgrep
|
||||
seed: 'Eve',
|
||||
type: 'sr25519'
|
||||
},
|
||||
{
|
||||
p: '0x1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c',
|
||||
s: '0xb835c20f450079cf4f513900ae9faf8df06ad86c681884122c752a4b2bf74d4303e4f21bc6cc62bb4eeed5a9cce642c25e2d2ac1464093b50f6196d78e3a7426', // nosemgrep
|
||||
seed: 'Ferdie',
|
||||
type: 'sr25519'
|
||||
}
|
||||
];
|
||||
|
||||
export const PAIRSETHEREUM: PairDef[] = [
|
||||
{
|
||||
name: 'Alith',
|
||||
p: '0x02509540919faacf9ab52146c9aa40db68172d83777250b28e4679176e49ccdd9f',
|
||||
s: '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Baltathar',
|
||||
p: '0x033bc19e36ff1673910575b6727a974a9abd80c9a875d41ab3e2648dbfb9e4b518',
|
||||
s: '0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Charleth',
|
||||
p: '0x0234637bdc0e89b5d46543bcbf8edff329d2702bc995e27e9af4b1ba009a3c2a5e',
|
||||
s: '0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Dorothy',
|
||||
p: '0x02a00d60b2b408c2a14c5d70cdd2c205db8985ef737a7e55ad20ea32cc9e7c417c',
|
||||
s: '0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Ethan',
|
||||
p: '0x025cdc005b752651cd3f728fb9192182acb3a9c89e19072cbd5b03f3ee1f1b3ffa',
|
||||
s: '0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4', // nosemgrep
|
||||
type: 'ethereum'
|
||||
},
|
||||
{
|
||||
name: 'Faith',
|
||||
p: '0x037964b6c9d546da4646ada28a99e34acaa1d14e7aba861a9055f9bd200c8abf74',
|
||||
s: '0xb9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df', // nosemgrep
|
||||
type: 'ethereum'
|
||||
}
|
||||
];
|
||||
|
||||
function createMeta (name?: string, seed?: string) {
|
||||
if (!name && !seed) {
|
||||
throw new Error('Testing pair should have either a name or a seed');
|
||||
}
|
||||
|
||||
return {
|
||||
isTesting: true,
|
||||
name: name || seed?.replace('//', '_').toLowerCase()
|
||||
};
|
||||
interface DevAccount {
|
||||
name: string;
|
||||
/** The byte the 32-byte seed is filled with. */
|
||||
seed: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name testKeyring
|
||||
* @summary Create an instance of Keyring pre-populated with locked test accounts
|
||||
* @description The test accounts (i.e. alice, bob, dave, eve, ferdie)
|
||||
* are available on the dev chain and each test account is initialized with DOT funds.
|
||||
* The accounts a Quantus dev chain endows at genesis.
|
||||
*
|
||||
* From `chain:primitives/dilithium-crypto/src/pair.rs`: ML-DSA-87 keys generated
|
||||
* directly from a 32-byte seed filled with one byte, with no HD derivation. Their
|
||||
* account ids are pinned against `quantus-cli` in `@quantus/crypto`'s tests
|
||||
* (`dev_account_ids_match_the_cli`), e.g. crystal_alice is
|
||||
* `qzk1Nxai3dZD9Cn5kwGcgL6mKxsfxwqdis7kDQJ52aJS2vSn7`.
|
||||
*
|
||||
* They replace upstream's sr25519 Alice…Ferdie and Ethereum Alith…Faith, which
|
||||
* this keyring can no longer hold. quantus/common#6
|
||||
*/
|
||||
export function createTestKeyring (options: KeyringOptions = {}, isDerived = true): KeyringInstance {
|
||||
const keyring = new Keyring(options);
|
||||
const pairs = options.type === 'ethereum'
|
||||
? PAIRSETHEREUM
|
||||
: PAIRSSR25519;
|
||||
export const DEV_ACCOUNTS: DevAccount[] = [
|
||||
{ name: 'crystal_alice', seed: 0 },
|
||||
{ name: 'dilithium_bob', seed: 1 },
|
||||
{ name: 'crystal_charlie', seed: 2 }
|
||||
];
|
||||
|
||||
for (const { name, p, s, seed, type } of pairs) {
|
||||
const meta = createMeta(name, seed);
|
||||
const pair = !isDerived && !name && seed
|
||||
? keyring.addFromUri(seed, meta, options.type)
|
||||
: keyring.addPair(
|
||||
createPair(
|
||||
{ toSS58: keyring.encodeAddress, type },
|
||||
{ publicKey: hexToU8a(p), secretKey: hexToU8a(s) },
|
||||
meta
|
||||
)
|
||||
);
|
||||
/**
|
||||
* @name testKeyring
|
||||
* @summary Create an instance of Keyring pre-populated with the dev accounts
|
||||
* @description crystal_alice, dilithium_bob and crystal_charlie, as a Quantus
|
||||
* dev chain endows them. They are always ML-DSA-87, whatever `options.type` says,
|
||||
* because that is what the chain made them; `options` sets the keyring's default
|
||||
* type and SS58 format for anything added afterwards. `_isDerived` is accepted for
|
||||
* upstream's signature and ignored: these accounts have no derivation.
|
||||
*/
|
||||
export function createTestKeyring (options: KeyringOptions = {}, _isDerived = true): KeyringInstance {
|
||||
const keyring = new Keyring(options);
|
||||
|
||||
for (const { name, seed } of DEV_ACCOUNTS) {
|
||||
const pair = keyring.addFromSeed(new Uint8Array(32).fill(seed), { isTesting: true, name }, 'dilithium87');
|
||||
|
||||
pair.lock = (): void => {
|
||||
// we don't have lock/unlock functionality here
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
import { cryptoWaitReady } from '@polkadot/util-crypto';
|
||||
|
||||
import Keyring from './index.js';
|
||||
import { createTestPairs } from './testingPairs.js';
|
||||
|
||||
const TEST_ADD = '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('testingPairs', (): void => {
|
||||
it('creates without failing', (): void => {
|
||||
expect(
|
||||
Object.keys(createTestPairs())
|
||||
).toHaveLength(2 + 0 + 7); // stash, session, pairs
|
||||
});
|
||||
|
||||
it('has the correct address for Alice (non-HDKD)', (): void => {
|
||||
expect(
|
||||
createTestPairs({ type: 'ed25519' }, false).alice.address
|
||||
).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');
|
||||
});
|
||||
|
||||
it('has the correct address for Alice (HDKD)', (): void => {
|
||||
expect(
|
||||
createTestPairs({ type: 'ed25519' }).alice.address
|
||||
).toEqual('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY');
|
||||
});
|
||||
|
||||
it('has the correct address for Alith (Eth)', (): void => {
|
||||
expect(
|
||||
createTestPairs({ type: 'ethereum' }).Alith.address
|
||||
).toEqual(TEST_ADD);
|
||||
});
|
||||
|
||||
it('has the correct address for Alith (Eth), same as obtained by createFromUri', (): void => {
|
||||
const keyring = new Keyring({ type: 'ethereum' });
|
||||
const pair = keyring.createFromUri('0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133');
|
||||
|
||||
expect(pair?.address).toEqual(TEST_ADD);
|
||||
});
|
||||
|
||||
describe('checks eth test addresses', (): void => {
|
||||
const ring = createTestPairs({ type: 'ethereum' });
|
||||
const keyring = new Keyring({ type: 'ethereum' });
|
||||
// priv keys generated by ganache-cli --mnemonic "bottom drive obey lake curtain smoke basket hold race lonely fit walk"
|
||||
const privKeys: string[] = ['0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133',
|
||||
'0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b',
|
||||
'0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b',
|
||||
'0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68',
|
||||
'0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4',
|
||||
'0xb9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df',
|
||||
'0x96b8a38e12e1a31dee1eab2fffdf9d9990045f5b37e44d8cc27766ef294acf18',
|
||||
'0x0d6dcaaef49272a5411896be8ad16c01c35d6f8c18873387b71fbc734759b0ab',
|
||||
'0x4c42532034540267bf568198ccec4cb822a025da542861fcb146a5fab6433ff8',
|
||||
'0x94c49300a58d576011096bcb006aa06f5a91b34b4383891e8029c21dc39fbb8b'];
|
||||
|
||||
// @ts-expect-error We should not delete from the maps, however this is a test
|
||||
delete ring.nobody;
|
||||
|
||||
Object
|
||||
.keys(ring)
|
||||
.filter((_, i) => i < 6)
|
||||
.forEach((testKeyring, i) => {
|
||||
it(`checks #${i}`, (): void => {
|
||||
expect(
|
||||
u8aToHex(ring[testKeyring].publicKey)
|
||||
).toEqual(
|
||||
u8aToHex(keyring.createFromUri(privKeys[i]).publicKey)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2017-2026 @polkadot/keyring authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeypairType } from '@polkadot/util-crypto/types';
|
||||
import type { KeyringOptions, KeyringPair } from './types.js';
|
||||
|
||||
import { nobody } from './pair/nobody.js';
|
||||
@@ -9,39 +8,17 @@ import { createTestKeyring } from './testing.js';
|
||||
|
||||
export interface TestKeyringMap {
|
||||
nobody: KeyringPair;
|
||||
|
||||
[index: string]: KeyringPair;
|
||||
}
|
||||
|
||||
export interface TestKeyringMapSubstrate extends TestKeyringMap {
|
||||
alice: KeyringPair;
|
||||
bob: KeyringPair;
|
||||
charlie: KeyringPair;
|
||||
dave: KeyringPair;
|
||||
eve: KeyringPair;
|
||||
ferdie: KeyringPair;
|
||||
/** The Quantus dev accounts, by name. See `DEV_ACCOUNTS`. */
|
||||
export interface TestKeyringMapQuantus extends TestKeyringMap {
|
||||
crystal_alice: KeyringPair;
|
||||
crystal_charlie: KeyringPair;
|
||||
dilithium_bob: KeyringPair;
|
||||
}
|
||||
|
||||
export interface TestKeyringMapEthereum extends TestKeyringMap {
|
||||
Alith: KeyringPair;
|
||||
Baltathar: KeyringPair;
|
||||
Charleth: KeyringPair;
|
||||
Dorothy: KeyringPair;
|
||||
Ethan: KeyringPair;
|
||||
Faith: KeyringPair;
|
||||
}
|
||||
|
||||
export type DetectMap<O extends KeyringOptions | undefined> = DetectPairType<O> extends 'ethereum'
|
||||
? TestKeyringMapEthereum
|
||||
: TestKeyringMapSubstrate;
|
||||
|
||||
export type DetectPairType<O extends KeyringOptions | undefined> = O extends KeyringOptions
|
||||
? O['type'] extends KeypairType
|
||||
? O['type']
|
||||
: 'sr25519'
|
||||
: 'sr25519';
|
||||
|
||||
export function createTestPairs <O extends KeyringOptions, M = DetectMap<O>> (options?: O, isDerived = true): M {
|
||||
export function createTestPairs <O extends KeyringOptions, M = TestKeyringMapQuantus> (options?: O, isDerived = true): M {
|
||||
const keyring = createTestKeyring(options, isDerived);
|
||||
const pairs = keyring.getPairs();
|
||||
const map: TestKeyringMap = { nobody: nobody() };
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { EncryptedJson, Keypair, KeypairType, Prefix } from '@polkadot/util
|
||||
export interface KeyringOptions {
|
||||
/** The ss58Format to use for address encoding (defaults to 42) */
|
||||
ss58Format?: Prefix;
|
||||
/** The type of keyring to create (defaults to ed25519) */
|
||||
/** The type of keyring to create (defaults to dilithium65, ML-DSA-65) */
|
||||
type?: KeypairType;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,11 @@ export interface KeyringPair {
|
||||
sign (message: string | Uint8Array, options?: SignOptions): Uint8Array;
|
||||
toJson (passphrase?: string): KeyringPair$Json;
|
||||
unlock (passphrase?: string): void;
|
||||
verify (message: string | Uint8Array, signature: Uint8Array, signerPublic: string | Uint8Array): boolean;
|
||||
/**
|
||||
* Verify an ML-DSA signature, bare or as the chain's `signature ‖ publicKey`,
|
||||
* under `context` (default: the empty context used for raw bytes).
|
||||
*/
|
||||
verify (message: string | Uint8Array, signature: Uint8Array, signerPublic: string | Uint8Array, context?: Uint8Array): boolean;
|
||||
vrfSign (message: string | Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): Uint8Array;
|
||||
vrfVerify (message: string | Uint8Array, vrfResult: Uint8Array, signerPublic: string | Uint8Array, context?: string | Uint8Array, extra?: string | Uint8Array): boolean;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "14.0.3-quantus.2",
|
||||
"version": "14.0.3-quantus.3",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@polkadot/util": "14.0.3",
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/networks', path: 'auto', type: 'auto', version: '14.0.3-quantus.2' };
|
||||
export const packageInfo = { name: '@polkadot/networks', path: 'auto', type: 'auto', version: '14.0.3-quantus.3' };
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"./packageDetect.cjs"
|
||||
],
|
||||
"type": "module",
|
||||
"version": "14.0.3-quantus.2",
|
||||
"version": "14.0.3-quantus.3",
|
||||
"browser": {
|
||||
"crypto": false,
|
||||
"stream": false
|
||||
@@ -29,13 +29,13 @@
|
||||
"dependencies": {
|
||||
"@noble/curves": "^1.3.0",
|
||||
"@noble/hashes": "^1.3.3",
|
||||
"@polkadot/networks": "14.0.3-quantus.2",
|
||||
"@polkadot/networks": "14.0.3-quantus.3",
|
||||
"@polkadot/util": "14.0.3",
|
||||
"@polkadot/wasm-crypto": "^7.5.3",
|
||||
"@polkadot/wasm-util": "^7.5.3",
|
||||
"@polkadot/x-bigint": "14.0.3",
|
||||
"@polkadot/x-randomvalues": "14.0.3",
|
||||
"@quantus/crypto": "^0.1.0",
|
||||
"@quantus/crypto": "^0.3.0",
|
||||
"@scure/base": "^1.1.7",
|
||||
"@scure/sr25519": "^0.2.0",
|
||||
"tslib": "^2.8.0"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { deriveAddress } from './index.js';
|
||||
|
||||
describe('deriveAddress', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
it('derives a known path', (): void => {
|
||||
expect(
|
||||
deriveAddress('5CZtJLXtVzrBJq1fMWfywDa6XuRwXekGdShPR4b8i9GWSbzB', '/joe/polkadot/0')
|
||||
).toEqual('5GZ4srnepXvdsuNVoxCGyVZd8ScDm4gkGLTKuaGARy9akjTa');
|
||||
});
|
||||
|
||||
it('fails on hard paths', (): void => {
|
||||
expect(
|
||||
() => deriveAddress('5CZtJLXtVzrBJq1fMWfywDa6XuRwXekGdShPR4b8i9GWSbzB', '//bob')
|
||||
).toThrow(/Expected suri to contain a combination of non-hard paths/);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DeriveJunction } from '../key/DeriveJunction.js';
|
||||
import type { Prefix } from './types.js';
|
||||
|
||||
import { keyExtractPath } from '../key/index.js';
|
||||
import { sr25519DerivePublic } from '../sr25519/index.js';
|
||||
import { decodeAddress } from './decode.js';
|
||||
import { encodeAddress } from './encode.js';
|
||||
|
||||
function filterHard ({ isHard }: DeriveJunction): boolean {
|
||||
return isHard;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 function deriveAddress (who: string | Uint8Array, suri: string, ss58Format?: Prefix): string {
|
||||
const { path } = keyExtractPath(suri);
|
||||
|
||||
if (!path.length || path.every(filterHard)) {
|
||||
throw new Error('Expected suri to contain a combination of non-hard paths');
|
||||
}
|
||||
|
||||
let publicKey = decodeAddress(who);
|
||||
|
||||
for (const { chainCode } of path) {
|
||||
publicKey = sr25519DerivePublic(publicKey, chainCode);
|
||||
}
|
||||
|
||||
return encodeAddress(publicKey, ss58Format);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HashType } from '../secp256k1/types.js';
|
||||
import type { HashType } from './hasher.js';
|
||||
import type { Prefix } from './types.js';
|
||||
|
||||
import { u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { hasher } from '../secp256k1/hasher.js';
|
||||
import { encodeAddress } from './encode.js';
|
||||
import { hasher } from './hasher.js';
|
||||
|
||||
/**
|
||||
* @name evmToAddress
|
||||
|
||||
+3
-2
@@ -1,11 +1,12 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HashType } from './types.js';
|
||||
|
||||
/** Which hash an EVM ↔ Substrate address conversion uses. */
|
||||
import { blake2AsU8a } from '../blake2/index.js';
|
||||
import { keccakAsU8a } from '../keccak/index.js';
|
||||
|
||||
export type HashType = 'blake2' | 'keccak';
|
||||
|
||||
export function hasher (hashType: HashType, data: Uint8Array | string, onlyJs?: boolean): Uint8Array {
|
||||
return hashType === 'keccak'
|
||||
? keccakAsU8a(data, undefined, onlyJs)
|
||||
@@ -5,7 +5,6 @@ export { addressToEvm } from './addressToEvm.js';
|
||||
export { checkAddress } from './check.js';
|
||||
export { checkAddressChecksum } from './checksum.js';
|
||||
export { decodeAddress } from './decode.js';
|
||||
export { deriveAddress } from './derive.js';
|
||||
export { encodeAddress } from './encode.js';
|
||||
export { encodeDerivedAddress } from './encodeDerived.js';
|
||||
export { encodeMultiAddress } from './encodeMulti.js';
|
||||
|
||||
@@ -15,7 +15,6 @@ export * from './blake2/index.js';
|
||||
export * from './crypto.js';
|
||||
export * from './dilithium.js';
|
||||
export * from './dilithium/index.js';
|
||||
export * from './ed25519/index.js';
|
||||
export * from './ethereum/index.js';
|
||||
export * from './hd/index.js';
|
||||
export * from './hmac/index.js';
|
||||
@@ -28,8 +27,5 @@ export * from './networks.js';
|
||||
export * from './pbkdf2/index.js';
|
||||
export * from './random/index.js';
|
||||
export * from './scrypt/index.js';
|
||||
export * from './secp256k1/index.js';
|
||||
export * from './sha/index.js';
|
||||
export * from './signature/index.js';
|
||||
export * from './sr25519/index.js';
|
||||
export * from './xxhash/index.js';
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { compactAddLength, isU8a, stringToU8a, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { blake2AsU8a } from '../blake2/asU8a.js';
|
||||
|
||||
const HDKD = compactAddLength(stringToU8a('Ed25519HDKD'));
|
||||
|
||||
export function ed25519DeriveHard (seed: Uint8Array, chainCode: Uint8Array): Uint8Array {
|
||||
if (!isU8a(chainCode) || chainCode.length !== 32) {
|
||||
throw new Error('Invalid chainCode passed to derive');
|
||||
}
|
||||
|
||||
return blake2AsU8a(
|
||||
u8aConcat(HDKD, seed, chainCode)
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Implements ed25519 operations
|
||||
*/
|
||||
export { ed25519DeriveHard } from './deriveHard.js';
|
||||
export { ed25519PairFromRandom } from './pair/fromRandom.js';
|
||||
export { ed25519PairFromSecret } from './pair/fromSecret.js';
|
||||
export { ed25519PairFromSeed } from './pair/fromSeed.js';
|
||||
export { ed25519PairFromString } from './pair/fromString.js';
|
||||
export { ed25519Sign } from './sign.js';
|
||||
export { ed25519Verify } from './verify.js';
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { ed25519PairFromRandom } from '../index.js';
|
||||
|
||||
describe('ed25519PairFromRandom', (): void => {
|
||||
let keypair: Keypair;
|
||||
|
||||
beforeEach((): void => {
|
||||
keypair = ed25519PairFromRandom();
|
||||
});
|
||||
|
||||
it('generates a valid publicKey', (): void => {
|
||||
expect(
|
||||
keypair.publicKey
|
||||
).toHaveLength(32);
|
||||
});
|
||||
|
||||
it('generates a valid secretKey', (): void => {
|
||||
expect(
|
||||
keypair.secretKey
|
||||
).toHaveLength(64);
|
||||
});
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { randomAsU8a } from '../../random/index.js';
|
||||
import { ed25519PairFromSeed } from './fromSeed.js';
|
||||
|
||||
/**
|
||||
* @name ed25519PairFromRandom
|
||||
* @summary Creates a new public/secret keypair.
|
||||
* @description
|
||||
* Returns a new generate object containing a `publicKey` & `secretKey`.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ed25519PairFromRandom } from '@polkadot/util-crypto';
|
||||
*
|
||||
* ed25519PairFromRandom(); // => { secretKey: [...], publicKey: [...] }
|
||||
* ```
|
||||
*/
|
||||
export function ed25519PairFromRandom (): Keypair {
|
||||
return ed25519PairFromSeed(randomAsU8a());
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { ed25519PairFromSecret } from '../index.js';
|
||||
|
||||
describe('ed25519PairFromSecret', (): void => {
|
||||
const secretKey = new Uint8Array([
|
||||
18, 52, 86, 120, 144, 18, 52, 86,
|
||||
120, 144, 18, 52, 86, 120, 144, 18,
|
||||
18, 52, 86, 120, 144, 18, 52, 86,
|
||||
120, 144, 18, 52, 86, 120, 144, 18,
|
||||
180, 114, 93, 155, 165, 255, 217, 82,
|
||||
16, 250, 209, 11, 193, 10, 88, 218,
|
||||
190, 190, 41, 193, 236, 252, 1, 152,
|
||||
216, 214, 0, 41, 45, 138, 13, 53
|
||||
]);
|
||||
|
||||
it('generates a valid publicKey/secretKey pair', (): void => {
|
||||
expect(
|
||||
ed25519PairFromSecret(secretKey)
|
||||
).toEqual({
|
||||
publicKey: new Uint8Array([
|
||||
180, 114, 93, 155, 165, 255, 217, 82,
|
||||
16, 250, 209, 11, 193, 10, 88, 218,
|
||||
190, 190, 41, 193, 236, 252, 1, 152,
|
||||
216, 214, 0, 41, 45, 138, 13, 53
|
||||
]),
|
||||
secretKey
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
/**
|
||||
* @name ed25519PairFromSecret
|
||||
* @summary Creates a new public/secret keypair from a secret.
|
||||
* @description
|
||||
* Returns a object containing a `publicKey` & `secretKey` generated from the supplied secret.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ed25519PairFromSecret } from '@polkadot/util-crypto';
|
||||
*
|
||||
* ed25519PairFromSecret(...); // => { secretKey: [...], publicKey: [...] }
|
||||
* ```
|
||||
*/
|
||||
export function ed25519PairFromSecret (secretKey: Uint8Array): Keypair {
|
||||
if (secretKey.length !== 64) {
|
||||
throw new Error('Invalid secretKey provided');
|
||||
}
|
||||
|
||||
return {
|
||||
publicKey: secretKey.slice(32),
|
||||
secretKey
|
||||
};
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { ed25519PairFromSeed } from '../index.js';
|
||||
|
||||
describe('ed25519PairFromSeed', (): void => {
|
||||
// NOTE: Aligned with Rust test, b"12345678901234567890123456789012"
|
||||
const TEST = stringToU8a('12345678901234567890123456789012');
|
||||
const RESULT = {
|
||||
publicKey: new Uint8Array([
|
||||
0x2f, 0x8c, 0x61, 0x29, 0xd8, 0x16, 0xcf, 0x51,
|
||||
0xc3, 0x74, 0xbc, 0x7f, 0x08, 0xc3, 0xe6, 0x3e,
|
||||
0xd1, 0x56, 0xcf, 0x78, 0xae, 0xfb, 0x4a, 0x65,
|
||||
0x50, 0xd9, 0x7b, 0x87, 0x99, 0x79, 0x77, 0xee
|
||||
]),
|
||||
secretKey: new Uint8Array([
|
||||
49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50,
|
||||
// public part
|
||||
0x2f, 0x8c, 0x61, 0x29, 0xd8, 0x16, 0xcf, 0x51,
|
||||
0xc3, 0x74, 0xbc, 0x7f, 0x08, 0xc3, 0xe6, 0x3e,
|
||||
0xd1, 0x56, 0xcf, 0x78, 0xae, 0xfb, 0x4a, 0x65,
|
||||
0x50, 0xd9, 0x7b, 0x87, 0x99, 0x79, 0x77, 0xee
|
||||
])
|
||||
};
|
||||
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
it('generates a valid publicKey/secretKey pair (u8a)', (): void => {
|
||||
[true, false].forEach((onlyJs): void => {
|
||||
expect(
|
||||
ed25519PairFromSeed(TEST, onlyJs)
|
||||
).toEqual(RESULT);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { ed25519 } from '@noble/curves/ed25519';
|
||||
|
||||
import { hasBigInt, u8aConcatStrict } from '@polkadot/util';
|
||||
import { ed25519KeypairFromSeed, isReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
/**
|
||||
* @name ed25519PairFromSeed
|
||||
* @summary Creates a new public/secret keypair from a seed.
|
||||
* @description
|
||||
* Returns a object containing a `publicKey` & `secretKey` generated from the supplied seed.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ed25519PairFromSeed } from '@polkadot/util-crypto';
|
||||
*
|
||||
* ed25519PairFromSeed(...); // => { secretKey: [...], publicKey: [...] }
|
||||
* ```
|
||||
*/
|
||||
export function ed25519PairFromSeed (seed: Uint8Array, onlyJs?: boolean): Keypair {
|
||||
if (!hasBigInt || (!onlyJs && isReady())) {
|
||||
const full = ed25519KeypairFromSeed(seed);
|
||||
|
||||
return {
|
||||
publicKey: full.slice(32),
|
||||
secretKey: full.slice(0, 64)
|
||||
};
|
||||
}
|
||||
|
||||
const publicKey = ed25519.getPublicKey(seed);
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
secretKey: u8aConcatStrict([seed, publicKey])
|
||||
};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { ed25519PairFromString } from '../index.js';
|
||||
|
||||
describe('ed25519PairFromSeed', (): void => {
|
||||
it('generates a valid publicKey/secretKey pair', (): void => {
|
||||
expect(
|
||||
ed25519PairFromString('test')
|
||||
).toEqual({
|
||||
publicKey: new Uint8Array([188, 108, 179, 142, 36, 142, 76, 87, 77, 193, 147, 139, 254, 110, 196, 217, 117, 233, 167, 165, 250, 150, 247, 237, 198, 68, 129, 4, 211, 209, 136, 48]),
|
||||
secretKey: new Uint8Array([146, 139, 32, 54, 105, 67, 226, 175, 209, 30, 188, 14, 174, 46, 83, 169, 59, 241, 119, 164, 252, 243, 91, 204, 100, 213, 3, 112, 78, 101, 226, 2, 188, 108, 179, 142, 36, 142, 76, 87, 77, 193, 147, 139, 254, 110, 196, 217, 117, 233, 167, 165, 250, 150, 247, 237, 198, 68, 129, 4, 211, 209, 136, 48])
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
|
||||
import { blake2AsU8a } from '../../blake2/asU8a.js';
|
||||
import { ed25519PairFromSeed } from './fromSeed.js';
|
||||
|
||||
/**
|
||||
* @name ed25519PairFromString
|
||||
* @summary Creates a new public/secret keypair from a string.
|
||||
* @description
|
||||
* Returns a object containing a `publicKey` & `secretKey` generated from the supplied string. The string is hashed and the value used as the input seed.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ed25519PairFromString } from '@polkadot/util-crypto';
|
||||
*
|
||||
* ed25519PairFromString('test'); // => { secretKey: [...], publicKey: [...] }
|
||||
* ```
|
||||
*/
|
||||
export function ed25519PairFromString (value: string): Keypair {
|
||||
return ed25519PairFromSeed(
|
||||
blake2AsU8a(
|
||||
stringToU8a(value)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { perfWasm } from '../test/index.js';
|
||||
import { ed25519PairFromSeed, ed25519Sign } from './index.js';
|
||||
|
||||
const PAIR = ed25519PairFromSeed(
|
||||
stringToU8a('12345678901234567890123456789012')
|
||||
);
|
||||
|
||||
describe('ed25519Sign', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('returns a valid signature for the message', (): void => {
|
||||
expect(
|
||||
ed25519Sign(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
PAIR,
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(
|
||||
new Uint8Array([28, 58, 206, 239, 249, 70, 59, 191, 166, 40, 219, 218, 235, 170, 25, 79, 10, 94, 9, 197, 34, 126, 1, 150, 246, 68, 28, 238, 36, 26, 172, 163, 168, 90, 202, 211, 126, 246, 57, 212, 43, 24, 88, 197, 240, 113, 118, 76, 37, 81, 91, 110, 236, 50, 144, 134, 100, 223, 220, 238, 34, 185, 211, 7])
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
perfWasm('ed25519Sign', 250, (input, onlyJs) =>
|
||||
ed25519Sign(input, PAIR, onlyJs)
|
||||
);
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../types.js';
|
||||
|
||||
import { ed25519 } from '@noble/curves/ed25519';
|
||||
|
||||
import { hasBigInt, u8aToU8a } from '@polkadot/util';
|
||||
import { ed25519Sign as wasmSign, isReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
/**
|
||||
* @name ed25519Sign
|
||||
* @summary Signs a message using the supplied secretKey
|
||||
* @description
|
||||
* Returns message signature of `message`, using the `secretKey`.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ed25519Sign } from '@polkadot/util-crypto';
|
||||
*
|
||||
* ed25519Sign([...], [...]); // => [...]
|
||||
* ```
|
||||
*/
|
||||
export function ed25519Sign (message: string | Uint8Array, { publicKey, secretKey }: Partial<Keypair>, onlyJs?: boolean): Uint8Array {
|
||||
if (!secretKey) {
|
||||
throw new Error('Expected a valid secretKey');
|
||||
} else if (!publicKey) {
|
||||
throw new Error('Expected a valid publicKey');
|
||||
}
|
||||
|
||||
const messageU8a = u8aToU8a(message);
|
||||
const privateU8a = secretKey.subarray(0, 32);
|
||||
|
||||
return !hasBigInt || (!onlyJs && isReady())
|
||||
? wasmSign(publicKey, privateU8a, messageU8a)
|
||||
: ed25519.sign(messageU8a, privateU8a);
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { ed25519PairFromSeed, ed25519Verify } from './index.js';
|
||||
|
||||
describe('ed25519Verify', (): void => {
|
||||
let publicKey: Uint8Array;
|
||||
let signature: Uint8Array;
|
||||
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
|
||||
publicKey = ed25519PairFromSeed(
|
||||
stringToU8a('12345678901234567890123456789012')
|
||||
).publicKey;
|
||||
signature = new Uint8Array([28, 58, 206, 239, 249, 70, 59, 191, 166, 40, 219, 218, 235, 170, 25, 79, 10, 94, 9, 197, 34, 126, 1, 150, 246, 68, 28, 238, 36, 26, 172, 163, 168, 90, 202, 211, 126, 246, 57, 212, 43, 24, 88, 197, 240, 113, 118, 76, 37, 81, 91, 110, 236, 50, 144, 134, 100, 223, 220, 238, 34, 185, 211, 7]);
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('validates a correctly signed message', (): void => {
|
||||
expect(
|
||||
ed25519Verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
signature,
|
||||
publicKey,
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('fails a correctly signed message (message changed)', (): void => {
|
||||
expect(
|
||||
ed25519Verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64, 0x65]),
|
||||
signature,
|
||||
publicKey,
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('fails a correctly signed message (signature changed)', (): void => {
|
||||
signature[0] = 0xff;
|
||||
|
||||
expect(
|
||||
ed25519Verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
signature,
|
||||
publicKey,
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(false);
|
||||
});
|
||||
|
||||
it('throws error when publicKey lengths do not match', (): void => {
|
||||
expect(
|
||||
() => ed25519Verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
signature,
|
||||
new Uint8Array([1, 2]),
|
||||
onlyJs
|
||||
)
|
||||
).toThrow(/Invalid publicKey/);
|
||||
});
|
||||
|
||||
it('throws error when signature lengths do not match', (): void => {
|
||||
expect(
|
||||
() => ed25519Verify(
|
||||
new Uint8Array([0x61, 0x62, 0x63, 0x64]),
|
||||
new Uint8Array([1, 2]),
|
||||
publicKey,
|
||||
onlyJs
|
||||
)
|
||||
).toThrow(/Invalid signature/);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { ed25519 } from '@noble/curves/ed25519';
|
||||
|
||||
import { hasBigInt, u8aToU8a } from '@polkadot/util';
|
||||
import { ed25519Verify as wasmVerify, isReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
/**
|
||||
* @name ed25519Sign
|
||||
* @summary Verifies the signature on the supplied message.
|
||||
* @description
|
||||
* Verifies the `signature` on `message` with the supplied `publicKey`. Returns `true` on sucess, `false` otherwise.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import { ed25519Verify } from '@polkadot/util-crypto';
|
||||
*
|
||||
* ed25519Verify([...], [...], [...]); // => true/false
|
||||
* ```
|
||||
*/
|
||||
export function ed25519Verify (message: string | Uint8Array, signature: string | Uint8Array, publicKey: string | Uint8Array, onlyJs?: boolean): boolean {
|
||||
const messageU8a = u8aToU8a(message);
|
||||
const publicKeyU8a = u8aToU8a(publicKey);
|
||||
const signatureU8a = u8aToU8a(signature);
|
||||
|
||||
if (publicKeyU8a.length !== 32) {
|
||||
throw new Error(`Invalid publicKey, received ${publicKeyU8a.length}, expected 32`);
|
||||
} else if (signatureU8a.length !== 64) {
|
||||
throw new Error(`Invalid signature, received ${signatureU8a.length} bytes, expected 64`);
|
||||
}
|
||||
|
||||
try {
|
||||
return !hasBigInt || (!onlyJs && isReady())
|
||||
? wasmVerify(signatureU8a, messageU8a, publicKeyU8a)
|
||||
: ed25519.verify(signatureU8a, messageU8a, publicKeyU8a);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ describe('formatAddress', () => {
|
||||
it('returns fails on invalid address', () => {
|
||||
expect(
|
||||
() => ethereumEncode('0xnotaddress')
|
||||
).toThrow(/Invalid address or publicKey provided/);
|
||||
).toThrow(/Invalid address provided/);
|
||||
});
|
||||
|
||||
it('converts lowercase to the checksummed address', () => {
|
||||
@@ -36,16 +36,15 @@ describe('formatAddress', () => {
|
||||
describe('from publicKey', (): void => {
|
||||
const ADDRESS = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
|
||||
|
||||
it('encodes a compressed publicKey', (): void => {
|
||||
// Deriving an address from a secp256k1 key needs the curve, which is gone
|
||||
// (quantus/common#6). Refused with the reason, not reported as a bad length.
|
||||
it('refuses a compressed or expanded secp256k1 publicKey', (): void => {
|
||||
expect(
|
||||
ethereumEncode('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
).toEqual(ADDRESS);
|
||||
});
|
||||
|
||||
it('encodes an expanded publicKey', (): void => {
|
||||
() => ethereumEncode('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
).toThrow(/secp256k1 is quantum-unsafe and has been removed/);
|
||||
expect(
|
||||
ethereumEncode('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
).toEqual(ADDRESS);
|
||||
() => ethereumEncode('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
).toThrow(/secp256k1 is quantum-unsafe and has been removed/);
|
||||
});
|
||||
|
||||
it('encodes a pre-hashed key', (): void => {
|
||||
|
||||
@@ -6,13 +6,17 @@ import type { HexString } from '@polkadot/util/types';
|
||||
import { u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
import { keccakAsU8a } from '../keccak/index.js';
|
||||
import { secp256k1Expand } from '../secp256k1/index.js';
|
||||
|
||||
/**
|
||||
* Formats an Ethereum address for display: EIP-55 checksum casing.
|
||||
*
|
||||
* Kept, though secp256k1 is gone, because it holds no key: `@polkadot/types`
|
||||
* and the identicon renderer format 20-byte addresses with it, and a wallet has
|
||||
* to be able to *show* an Ethereum address to recognise and refuse one. What is
|
||||
* gone is deriving an address from a secp256k1 public key, which needed the
|
||||
* curve. quantus/common#6
|
||||
*/
|
||||
function getH160 (u8a: Uint8Array): Uint8Array {
|
||||
if ([33, 65].includes(u8a.length)) {
|
||||
u8a = keccakAsU8a(secp256k1Expand(u8a));
|
||||
}
|
||||
|
||||
return u8a.slice(-20);
|
||||
}
|
||||
|
||||
@@ -23,8 +27,10 @@ export function ethereumEncode (addressOrPublic?: string | Uint8Array): HexStrin
|
||||
|
||||
const u8aAddress = u8aToU8a(addressOrPublic);
|
||||
|
||||
if (![20, 32, 33, 65].includes(u8aAddress.length)) {
|
||||
throw new Error(`Invalid address or publicKey provided, received ${u8aAddress.length} bytes input`);
|
||||
if ([33, 65].includes(u8aAddress.length)) {
|
||||
throw new Error('Deriving an Ethereum address from a secp256k1 public key is not supported: secp256k1 is quantum-unsafe and has been removed');
|
||||
} else if (![20, 32].includes(u8aAddress.length)) {
|
||||
throw new Error(`Invalid address provided, received ${u8aAddress.length} bytes input`);
|
||||
}
|
||||
|
||||
const address = u8aToHex(getH160(u8aAddress), -1, false);
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { mnemonicToLegacySeed } from '@polkadot/util-crypto';
|
||||
|
||||
import { hdEthereum } from './index.js';
|
||||
|
||||
describe('hdEthereum', (): void => {
|
||||
const PHRASE = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
||||
const derivationPath = 'm/44\'/60\'/0\'/0/0';
|
||||
const PUBLIC = new Uint8Array([
|
||||
3, 118, 64, 77, 247, 27, 4, 157,
|
||||
236, 206, 251, 221, 230, 244, 154, 147,
|
||||
189, 131, 249, 169, 102, 78, 3, 185,
|
||||
153, 19, 89, 40, 24, 25, 139, 131,
|
||||
93
|
||||
]);
|
||||
const SECRET = new Uint8Array([
|
||||
166, 162, 203, 17, 2, 206, 110, 176,
|
||||
18, 102, 230, 144, 90, 158, 25, 232,
|
||||
43, 180, 176, 49, 189, 149, 3, 71,
|
||||
243, 228, 223, 104, 125, 132, 58, 228
|
||||
]
|
||||
);
|
||||
const PUBLICDERIVED = new Uint8Array([
|
||||
3, 129, 53, 27, 27, 70, 210, 96,
|
||||
43, 9, 146, 187, 93, 85, 49, 249,
|
||||
193, 105, 107, 8, 18, 254, 178, 83,
|
||||
75, 104, 132, 173, 196, 126, 46, 29,
|
||||
139
|
||||
]);
|
||||
const SECRETDERIVED = new Uint8Array([
|
||||
7, 13, 195, 17, 115, 0, 1, 25,
|
||||
24, 226, 107, 2, 23, 105, 69, 204,
|
||||
21, 195, 213, 72, 207, 73, 253, 132,
|
||||
24, 217, 127, 147, 175, 105, 158, 70
|
||||
]);
|
||||
|
||||
it('derives the right key pair from a mnemonic', (): void => {
|
||||
const key = hdEthereum(mnemonicToLegacySeed(PHRASE, '', false, 64));
|
||||
|
||||
expect(key.publicKey).toEqual(PUBLIC);
|
||||
expect(key.secretKey).toEqual(SECRET);
|
||||
});
|
||||
|
||||
it('derives the right key pair from a mnemonic and a derivation path', (): void => {
|
||||
const key = hdEthereum(mnemonicToLegacySeed(PHRASE, '', false, 64), derivationPath);
|
||||
|
||||
expect(key.publicKey).toEqual(PUBLICDERIVED);
|
||||
expect(key.secretKey).toEqual(SECRETDERIVED);
|
||||
});
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { bnToU8a, stringToU8a, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { BN_BE_32_OPTS } from '../../bn.js';
|
||||
import { hmacShaAsU8a } from '../../hmac/index.js';
|
||||
import { secp256k1PairFromSeed, secp256k1PrivateKeyTweakAdd } from '../../secp256k1/index.js';
|
||||
import { HARDENED, hdValidatePath } from '../validatePath.js';
|
||||
|
||||
interface CodedKeypair extends Keypair {
|
||||
chainCode: Uint8Array;
|
||||
}
|
||||
|
||||
const MASTER_SECRET = stringToU8a('Bitcoin seed');
|
||||
|
||||
function createCoded (secretKey: Uint8Array, chainCode: Uint8Array): CodedKeypair {
|
||||
return {
|
||||
chainCode,
|
||||
publicKey: secp256k1PairFromSeed(secretKey).publicKey,
|
||||
secretKey
|
||||
};
|
||||
}
|
||||
|
||||
function deriveChild (hd: CodedKeypair, index: number): CodedKeypair {
|
||||
const indexBuffer = bnToU8a(index, BN_BE_32_OPTS);
|
||||
const data = index >= HARDENED
|
||||
? u8aConcat(new Uint8Array(1), hd.secretKey, indexBuffer)
|
||||
: u8aConcat(hd.publicKey, indexBuffer);
|
||||
|
||||
try {
|
||||
const I = hmacShaAsU8a(hd.chainCode, data, 512);
|
||||
|
||||
return createCoded(
|
||||
secp256k1PrivateKeyTweakAdd(hd.secretKey, I.slice(0, 32)),
|
||||
I.slice(32)
|
||||
);
|
||||
} catch {
|
||||
// In case parse256(IL) >= n or ki == 0, proceed with the next value for i
|
||||
return deriveChild(hd, index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
export function hdEthereum (seed: Uint8Array, path = ''): Keypair {
|
||||
const I = hmacShaAsU8a(MASTER_SECRET, seed, 512);
|
||||
let hd = createCoded(I.slice(0, 32), I.slice(32));
|
||||
|
||||
if (!path || path === 'm' || path === 'M' || path === "m'" || path === "M'") {
|
||||
return hd;
|
||||
}
|
||||
|
||||
if (!hdValidatePath(path)) {
|
||||
throw new Error('Invalid derivation path');
|
||||
}
|
||||
|
||||
const parts = path.split('/').slice(1);
|
||||
|
||||
for (const p of parts) {
|
||||
hd = deriveChild(hd, parseInt(p, 10) + (
|
||||
(p.length > 1) && p.endsWith("'")
|
||||
? HARDENED
|
||||
: 0
|
||||
));
|
||||
}
|
||||
|
||||
return hd;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { hdEthereum } from './ethereum/index.js';
|
||||
export { hdLedger } from './ledger/index.js';
|
||||
export { hdValidatePath } from './validatePath.js';
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { BN_EIGHT, bnToU8a, u8aConcat, u8aToBn } from '@polkadot/util';
|
||||
|
||||
import { BN_LE_32_OPTS, BN_LE_512_OPTS, BN_LE_OPTS } from '../../bn.js';
|
||||
import { hmacShaAsU8a } from '../../hmac/index.js';
|
||||
|
||||
// performs hard-only derivation on the xprv
|
||||
export function ledgerDerivePrivate (xprv: Uint8Array, index: number): Uint8Array {
|
||||
const kl = xprv.subarray(0, 32);
|
||||
const kr = xprv.subarray(32, 64);
|
||||
const cc = xprv.subarray(64, 96);
|
||||
const data = u8aConcat([0], kl, kr, bnToU8a(index, BN_LE_32_OPTS));
|
||||
const z = hmacShaAsU8a(cc, data, 512);
|
||||
|
||||
data[0] = 0x01;
|
||||
|
||||
return u8aConcat(
|
||||
bnToU8a(
|
||||
u8aToBn(kl, BN_LE_OPTS).iadd(
|
||||
u8aToBn(z.subarray(0, 28), BN_LE_OPTS).imul(BN_EIGHT)
|
||||
),
|
||||
BN_LE_512_OPTS
|
||||
).subarray(0, 32),
|
||||
bnToU8a(
|
||||
u8aToBn(kr, BN_LE_OPTS).iadd(
|
||||
u8aToBn(z.subarray(32, 64), BN_LE_OPTS)
|
||||
),
|
||||
BN_LE_512_OPTS
|
||||
).subarray(0, 32),
|
||||
hmacShaAsU8a(cc, data, 512).subarray(32, 64)
|
||||
);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { hdLedger } from '../index.js';
|
||||
|
||||
const MNE_0 = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const MNE_1 = 'open jelly jeans corn ketchup supreme brief element armed lens vault weather original scissors rug priority vicious lesson raven spot gossip powder person volcano';
|
||||
const MNE_P = `${MNE_1} testing`;
|
||||
|
||||
const TESTS = {
|
||||
Kusama: {
|
||||
slip44: 0x01b2,
|
||||
tests: [
|
||||
{
|
||||
ed25519: '0x98cb4e14e0e08ea876f88d728545ea7572dc07dbbe69f1731c418fb827e69d41',
|
||||
index: [0, 0],
|
||||
mnemonic: MNE_0
|
||||
},
|
||||
{
|
||||
ed25519: '0x70e9010e84c81095aaa5f63b1c5a6a66a1dcbec017a23c2f3b7a1b08fe5ea65a',
|
||||
index: [0, 0],
|
||||
mnemonic: MNE_1
|
||||
},
|
||||
{
|
||||
ed25519: '0xf06730efb1e6ea59ac752a7c3620fade3909062fb88597856cc3af72045fa65a',
|
||||
index: [5, 7],
|
||||
mnemonic: MNE_1
|
||||
}
|
||||
]
|
||||
},
|
||||
Polkadot: {
|
||||
slip44: 0x0162,
|
||||
tests: [
|
||||
{
|
||||
ed25519: '0xe8c68348586d53e4e8d1a864b0e4e17c75e4eb06e0c63c1432bef2ba29e69d41',
|
||||
index: [0, 0],
|
||||
mnemonic: MNE_0
|
||||
},
|
||||
{
|
||||
ed25519: '0x3890e8db837eba3f8f25215c753e1091062298ce671a51441e7ef89a7adc4f48',
|
||||
index: [0, 0],
|
||||
mnemonic: MNE_P
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
describe('ledgerDerive', (): void => {
|
||||
Object.entries(TESTS).forEach(([network, { slip44, tests }]): void => {
|
||||
tests.forEach(({ ed25519, index: [account, address], mnemonic }, index): void => {
|
||||
it(`derives a known ed25519 seed for ${network} (${index})`, (): void => {
|
||||
expect(u8aToHex(
|
||||
hdLedger(mnemonic, `m/44'/${slip44}'/${account}'/0'/${address}'`)
|
||||
.secretKey
|
||||
.slice(0, 32)
|
||||
)).toEqual(ed25519);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { ed25519PairFromSeed } from '../../ed25519/index.js';
|
||||
import { mnemonicValidate } from '../../mnemonic/index.js';
|
||||
import { HARDENED, hdValidatePath } from '../validatePath.js';
|
||||
import { ledgerDerivePrivate } from './derivePrivate.js';
|
||||
import { ledgerMaster } from './master.js';
|
||||
|
||||
export function hdLedger (_mnemonic: string, path: string): Keypair {
|
||||
const words = _mnemonic
|
||||
.split(' ')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s);
|
||||
|
||||
if (![12, 24, 25].includes(words.length)) {
|
||||
throw new Error('Expected a mnemonic with 24 words (or 25 including a password)');
|
||||
}
|
||||
|
||||
const [mnemonic, password] = words.length === 25
|
||||
? [words.slice(0, 24).join(' '), words[24]]
|
||||
: [words.join(' '), ''];
|
||||
|
||||
if (!mnemonicValidate(mnemonic)) {
|
||||
throw new Error('Invalid mnemonic passed to ledger derivation');
|
||||
} else if (!hdValidatePath(path)) {
|
||||
throw new Error('Invalid derivation path');
|
||||
}
|
||||
|
||||
const parts = path.split('/').slice(1);
|
||||
let seed = ledgerMaster(mnemonic, password);
|
||||
|
||||
for (const p of parts) {
|
||||
const n = parseInt(p.replace(/'$/, ''), 10);
|
||||
|
||||
seed = ledgerDerivePrivate(seed, (n < HARDENED) ? (n + HARDENED) : n);
|
||||
}
|
||||
|
||||
return ed25519PairFromSeed(seed.slice(0, 32));
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { ledgerMaster } from './master.js';
|
||||
|
||||
const MNEMONIC = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
||||
const XPRV = '0x402b03cd9c8bed9ba9f9bd6cd9c315ce9fcc59c7c25d37c85a36096617e69d418e35cb4a3b737afd007f0688618f21a8831643c0e6c77fc33c06026d2a0fc93832596435e70647d7d98ef102a32ea40319ca8fb6c851d7346d3bd8f9d1492658';
|
||||
|
||||
describe('ledgerDerive', (): void => {
|
||||
it('derives a known master xprv', (): void => {
|
||||
expect(u8aToHex(
|
||||
ledgerMaster(MNEMONIC)
|
||||
)).toEqual(XPRV);
|
||||
});
|
||||
});
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { hmacShaAsU8a } from '../../hmac/index.js';
|
||||
import { mnemonicToSeedSync } from '../../mnemonic/bip39.js';
|
||||
|
||||
const ED25519_CRYPTO = 'ed25519 seed';
|
||||
|
||||
// gets an xprv from a mnemonic
|
||||
export function ledgerMaster (mnemonic: string, password?: string): Uint8Array {
|
||||
const seed = mnemonicToSeedSync(mnemonic, password);
|
||||
const chainCode = hmacShaAsU8a(ED25519_CRYPTO, new Uint8Array([1, ...seed]), 256);
|
||||
let priv;
|
||||
|
||||
while (!priv || (priv[31] & 0b0010_0000)) {
|
||||
priv = hmacShaAsU8a(ED25519_CRYPTO, priv || seed, 512);
|
||||
}
|
||||
|
||||
priv[0] &= 0b1111_1000;
|
||||
priv[31] &= 0b0111_1111;
|
||||
priv[31] |= 0b0100_0000;
|
||||
|
||||
return u8aConcat(priv, chainCode);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair, KeypairType } from '../types.js';
|
||||
import type { DeriveJunction } from './DeriveJunction.js';
|
||||
|
||||
import { keyHdkdEcdsa } from './hdkdEcdsa.js';
|
||||
import { keyHdkdEd25519 } from './hdkdEd25519.js';
|
||||
import { keyHdkdSr25519 } from './hdkdSr25519.js';
|
||||
|
||||
const generators = {
|
||||
ecdsa: keyHdkdEcdsa,
|
||||
ed25519: keyHdkdEd25519,
|
||||
// FIXME This is Substrate-compatible, not Ethereum-compatible
|
||||
ethereum: keyHdkdEcdsa,
|
||||
sr25519: keyHdkdSr25519
|
||||
};
|
||||
|
||||
export function keyFromPath (pair: Keypair, path: DeriveJunction[], type: KeypairType): Keypair {
|
||||
const keyHdkd = generators[type as keyof typeof generators] as typeof keyHdkdEd25519 | undefined;
|
||||
|
||||
// ML-DSA has no junction derivation to fall back on. Lattice keys are not
|
||||
// publicly derivable, so a soft junction cannot mean what BIP-32 implies, and
|
||||
// the chain's own `Pair::derive` refuses for the same reason. Quantus keys
|
||||
// derive from a hardened BIP44 path instead — see quantus/common#4 — which is
|
||||
// a different entry point, not a different generator in this map.
|
||||
//
|
||||
// This throws rather than returning the pair unchanged: silently ignoring a
|
||||
// derivation path would hand the caller the *parent* key under a child's
|
||||
// name, and fund an address nobody asked for.
|
||||
if (!keyHdkd) {
|
||||
throw new Error(`Unable to derive on this keypair type: ${type}`);
|
||||
}
|
||||
|
||||
let result = pair;
|
||||
|
||||
for (const junction of path) {
|
||||
result = keyHdkd(result, junction);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../types.js';
|
||||
import type { DeriveJunction } from './DeriveJunction.js';
|
||||
|
||||
export function createSeedDeriveFn (fromSeed: (seed: Uint8Array) => Keypair, derive: (seed: Uint8Array, chainCode: Uint8Array) => Uint8Array): (keypair: Keypair, junction: DeriveJunction) => Keypair {
|
||||
return (keypair: Keypair, { chainCode, isHard }: DeriveJunction): Keypair => {
|
||||
if (!isHard) {
|
||||
throw new Error('A soft key was found in the path and is not supported');
|
||||
}
|
||||
|
||||
return fromSeed(
|
||||
derive(keypair.secretKey.subarray(0, 32), chainCode)
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { secp256k1DeriveHard } from '../secp256k1/deriveHard.js';
|
||||
import { secp256k1PairFromSeed } from '../secp256k1/pair/fromSeed.js';
|
||||
import { createSeedDeriveFn } from './hdkdDerive.js';
|
||||
|
||||
export const keyHdkdEcdsa = /*#__PURE__*/ createSeedDeriveFn(secp256k1PairFromSeed, secp256k1DeriveHard);
|
||||
@@ -1,7 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { ed25519DeriveHard, ed25519PairFromSeed } from '../ed25519/index.js';
|
||||
import { createSeedDeriveFn } from './hdkdDerive.js';
|
||||
|
||||
export const keyHdkdEd25519 = /*#__PURE__*/ createSeedDeriveFn(ed25519PairFromSeed, ed25519DeriveHard);
|
||||
@@ -1,14 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../types.js';
|
||||
import type { DeriveJunction } from './DeriveJunction.js';
|
||||
|
||||
import { sr25519DeriveHard } from '../sr25519/deriveHard.js';
|
||||
import { sr25519DeriveSoft } from '../sr25519/deriveSoft.js';
|
||||
|
||||
export function keyHdkdSr25519 (keypair: Keypair, { chainCode, isSoft }: DeriveJunction): Keypair {
|
||||
return isSoft
|
||||
? sr25519DeriveSoft(keypair, chainCode)
|
||||
: sr25519DeriveHard(keypair, chainCode);
|
||||
}
|
||||
@@ -6,7 +6,3 @@
|
||||
*/
|
||||
export { keyExtractPath } from './extractPath.js';
|
||||
export { keyExtractSuri } from './extractSuri.js';
|
||||
export { keyFromPath } from './fromPath.js';
|
||||
export { keyHdkdEcdsa } from './hdkdEcdsa.js';
|
||||
export { keyHdkdEd25519 } from './hdkdEd25519.js';
|
||||
export { keyHdkdSr25519 } from './hdkdSr25519.js';
|
||||
|
||||
+4
@@ -3,6 +3,10 @@
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
// The standard BIP39 test vectors (Trezor): mnemonic, entropy, seed, and
|
||||
// upstream's sr25519 expanded secret. Moved here from sr25519/ when that
|
||||
// directory was removed (quantus/common#6); the mnemonic and entropy columns are
|
||||
// what is still used.
|
||||
// mnemonic, entropy, seed, secret (expanded)
|
||||
type Test = [string, string, string, string];
|
||||
|
||||
@@ -7,5 +7,4 @@
|
||||
export { mnemonicGenerate } from './generate.js';
|
||||
export { mnemonicToEntropy } from './toEntropy.js';
|
||||
export { mnemonicToLegacySeed } from './toLegacySeed.js';
|
||||
export { mnemonicToMiniSecret } from './toMiniSecret.js';
|
||||
export { mnemonicValidate } from './validate.js';
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { cryptoWaitReady } from '../index.js';
|
||||
import tests from '../sr25519/pair/testing.spec.js';
|
||||
import { french as frenchWords } from './wordlists/index.js';
|
||||
import tests from './bip39Vectors.spec.js';
|
||||
import { mnemonicToEntropy } from './toEntropy.js';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aEq, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { cryptoWaitReady } from '../index.js';
|
||||
import tests from '../sr25519/pair/testing.spec.js';
|
||||
import { korean as koreanWords } from './wordlists/index.js';
|
||||
import { mnemonicToMiniSecret } from './toMiniSecret.js';
|
||||
|
||||
const MNEMONIC = 'seed sock milk update focus rotate barely fade car face mechanic mercy';
|
||||
const SEED = '0x4d1ab2a57929edfd018aaa974e62ed557e3f54b4104acabedf73c8f5a1dbb029';
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
describe('mnemonicToMiniSecret', (): void => {
|
||||
for (const password of [undefined, 'foo', 'bar']) {
|
||||
it(`generates Wasm & Js equivalents for password=${password || 'undefined'}`, (): void => {
|
||||
expect(
|
||||
u8aEq(
|
||||
mnemonicToMiniSecret(MNEMONIC, password, undefined, true),
|
||||
mnemonicToMiniSecret(MNEMONIC, password, undefined, false)
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
}
|
||||
|
||||
it('creates a known minisecret from a non-english mnemonic', (): void => {
|
||||
const mnemonic = '엉덩이 능동적 숫자 팩시밀리 비난 서적 파출소 도움 독창적 인생 상류 먼지 답변 음반 수박 사업 노란색 공사 우체국 특급 도대체 금지 굉장히 고무신';
|
||||
|
||||
expect(
|
||||
() => mnemonicToMiniSecret(mnemonic, 'testing')
|
||||
).toThrow();
|
||||
expect(
|
||||
u8aToHex(mnemonicToMiniSecret(mnemonic, 'testing', koreanWords))
|
||||
).toEqual('0xefa278a62535581767a2f49cb542ed91b65fb911e1b05e7a09c702b257f10c13');
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('generates a valid seed', (): void => {
|
||||
expect(
|
||||
u8aToHex(mnemonicToMiniSecret(MNEMONIC, undefined, undefined, onlyJs))
|
||||
).toEqual(SEED);
|
||||
});
|
||||
|
||||
it('fails with non-mnemonics', (): void => {
|
||||
expect(
|
||||
() => mnemonicToMiniSecret('foo bar baz', undefined, undefined, onlyJs)
|
||||
).toThrow(/mnemonic specified/);
|
||||
});
|
||||
|
||||
tests.forEach(([mnemonic, , seed], index): void => {
|
||||
it(`Created correct seed for ${index}`, (): void => {
|
||||
expect(
|
||||
u8aToHex(mnemonicToMiniSecret(mnemonic, 'Substrate', undefined, onlyJs))
|
||||
).toEqual(
|
||||
// mini returned here, only check first 32-bytes (64 hex + 2 prefix)
|
||||
seed.substring(0, 66)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
import { bip39ToMiniSecret, isReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { pbkdf2Encode } from '../pbkdf2/index.js';
|
||||
import { mnemonicToEntropy } from './toEntropy.js';
|
||||
import { mnemonicValidate } from './validate.js';
|
||||
|
||||
export function mnemonicToMiniSecret (mnemonic: string, password = '', wordlist?: string[], onlyJs?: boolean): Uint8Array {
|
||||
if (!mnemonicValidate(mnemonic, wordlist, onlyJs)) {
|
||||
throw new Error('Invalid bip39 mnemonic specified');
|
||||
} else if (!wordlist && !onlyJs && isReady()) {
|
||||
return bip39ToMiniSecret(mnemonic, password);
|
||||
}
|
||||
|
||||
const entropy = mnemonicToEntropy(mnemonic, wordlist);
|
||||
const salt = stringToU8a(`mnemonic${password}`);
|
||||
|
||||
// return the first 32 bytes as the seed
|
||||
return pbkdf2Encode(entropy, salt).password.slice(0, 32);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { arrayRange, u8aEq } from '@polkadot/util';
|
||||
|
||||
import { cryptoWaitReady, ed25519PairFromSeed, mnemonicGenerate, mnemonicToMiniSecret, sr25519PairFromSeed } from '../index.js';
|
||||
|
||||
// NOTE: This basically controls how long stuff runs for, YMMV
|
||||
//
|
||||
// - 100 runs with 5 checks, takes 2mins on _my_ machine
|
||||
// - 10_000 runs with 5 checks should be ~3hrs
|
||||
const NUM_RUNS = 100;
|
||||
const NUM_CHECKS = 5;
|
||||
|
||||
await cryptoWaitReady();
|
||||
|
||||
// generate either a JS or WASM mnemonic
|
||||
for (const onlyJsMnemonic of [false, true]) {
|
||||
describe(`mnemonicToMiniSecret (conpare), onlyJs${(onlyJsMnemonic && 'true') || 'false'}`, (): void => {
|
||||
for (const i of arrayRange(NUM_RUNS)) {
|
||||
// loop through lots of mnemonics
|
||||
describe(`run=${i + 1}`, (): void => {
|
||||
// compare both JS and WASM outputs against original
|
||||
for (const onlyJsMini of [false, true]) {
|
||||
describe(`onlyJsMini=${(onlyJsMini && 'true') || 'false'}`, (): void => {
|
||||
// NOTE we cannot actually use the onlyJsMnemonic flag here
|
||||
const mnemonic = mnemonicGenerate(12);
|
||||
|
||||
describe(`${mnemonic}`, (): void => {
|
||||
// do iterations to check and re-check that all matches
|
||||
for (const count of arrayRange(NUM_CHECKS)) {
|
||||
it(`check=${count + 1}`, (): void => {
|
||||
const minisecret = mnemonicToMiniSecret(mnemonic, count ? `${count}` : '', undefined, onlyJsMnemonic);
|
||||
const edpub = ed25519PairFromSeed(minisecret).publicKey;
|
||||
const srpub = sr25519PairFromSeed(minisecret).publicKey;
|
||||
const testmini = mnemonicToMiniSecret(mnemonic, count ? `${count}` : '', undefined, onlyJsMini);
|
||||
|
||||
// explicit minisecret compare
|
||||
expect(
|
||||
u8aEq(minisecret, testmini)
|
||||
).toEqual(true);
|
||||
|
||||
// compare the sr25519 keypair generated
|
||||
expect(
|
||||
u8aEq(srpub, sr25519PairFromSeed(testmini).publicKey)
|
||||
).toEqual(true);
|
||||
|
||||
// compare ed both in WASM and JS
|
||||
[true, false].forEach((onlyJsEd): void => {
|
||||
expect(
|
||||
u8aEq(edpub, ed25519PairFromSeed(testmini, onlyJsEd).publicKey)
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3,4 +3,4 @@
|
||||
|
||||
// Do not edit, auto-generated by @polkadot/dev
|
||||
|
||||
export const packageInfo = { name: '@polkadot/util-crypto', path: 'auto', type: 'auto', version: '14.0.3-quantus.2' };
|
||||
export const packageInfo = { name: '@polkadot/util-crypto', path: 'auto', type: 'auto', version: '14.0.3-quantus.3' };
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { perfWasm } from '../test/index.js';
|
||||
import { secp256k1Compress } from './index.js';
|
||||
|
||||
describe('secp256k1Compress', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('returns a compressed key as-is', (): void => {
|
||||
expect(
|
||||
secp256k1Compress(
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'),
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
);
|
||||
});
|
||||
|
||||
it('compresses a known key', (): void => {
|
||||
expect(
|
||||
secp256k1Compress(
|
||||
hexToU8a('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af'),
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
perfWasm('secp256k1Compress', 100000, (input, onlyJs) => secp256k1Compress(input, onlyJs), [[
|
||||
hexToU8a('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
]]
|
||||
);
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||
|
||||
import { hasBigInt } from '@polkadot/util';
|
||||
import { isReady, secp256k1Compress as wasm } from '@polkadot/wasm-crypto';
|
||||
|
||||
export function secp256k1Compress (publicKey: Uint8Array, onlyJs?: boolean): Uint8Array {
|
||||
if (![33, 65].includes(publicKey.length)) {
|
||||
throw new Error(`Invalid publicKey provided, received ${publicKey.length} bytes input`);
|
||||
}
|
||||
|
||||
if (publicKey.length === 33) {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
return !hasBigInt || (!onlyJs && isReady())
|
||||
? wasm(publicKey)
|
||||
: secp256k1.ProjectivePoint.fromHex(publicKey).toRawBytes(true);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { compactAddLength, isU8a, stringToU8a, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { blake2AsU8a } from '../blake2/asU8a.js';
|
||||
|
||||
const HDKD = compactAddLength(stringToU8a('Secp256k1HDKD'));
|
||||
|
||||
export function secp256k1DeriveHard (seed: Uint8Array, chainCode: Uint8Array): Uint8Array {
|
||||
if (!isU8a(chainCode) || chainCode.length !== 32) {
|
||||
throw new Error('Invalid chainCode passed to derive');
|
||||
}
|
||||
|
||||
// NOTE This is specific to the Substrate HDD derivation, so always use the blake2 hasher
|
||||
return blake2AsU8a(u8aConcat(HDKD, seed, chainCode), 256);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { perfWasm } from '../test/index.js';
|
||||
import { secp256k1Expand } from './index.js';
|
||||
|
||||
describe('secp256k1Expand', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('expands a known key', (): void => {
|
||||
expect(
|
||||
secp256k1Expand(
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077'),
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
);
|
||||
});
|
||||
|
||||
it('expands a known full key', (): void => {
|
||||
expect(
|
||||
secp256k1Expand(
|
||||
hexToU8a('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af'),
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(
|
||||
hexToU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
perfWasm('secp256k1Expand', 2000, (input, onlyJs) => secp256k1Expand(input, onlyJs), [[
|
||||
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
|
||||
]]
|
||||
);
|
||||
});
|
||||
@@ -1,30 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||
|
||||
import { bnToU8a, hasBigInt, u8aConcat } from '@polkadot/util';
|
||||
import { isReady, secp256k1Expand as wasm } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { BN_BE_256_OPTS } from '../bn.js';
|
||||
|
||||
export function secp256k1Expand (publicKey: Uint8Array, onlyJs?: boolean): Uint8Array {
|
||||
if (![33, 65].includes(publicKey.length)) {
|
||||
throw new Error(`Invalid publicKey provided, received ${publicKey.length} bytes input`);
|
||||
}
|
||||
|
||||
if (publicKey.length === 65) {
|
||||
return publicKey.subarray(1);
|
||||
}
|
||||
|
||||
if (!hasBigInt || (!onlyJs && isReady())) {
|
||||
return wasm(publicKey).subarray(1);
|
||||
}
|
||||
|
||||
const { x, y } = secp256k1.ProjectivePoint.fromHex(publicKey);
|
||||
|
||||
return u8aConcat(
|
||||
bnToU8a(x, BN_BE_256_OPTS),
|
||||
bnToU8a(y, BN_BE_256_OPTS)
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hasher } from './hasher.js';
|
||||
|
||||
describe('hasher', (): void => {
|
||||
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])
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { secp256k1Compress } from './compress.js';
|
||||
export { secp256k1Expand } from './expand.js';
|
||||
export { secp256k1PairFromSeed } from './pair/fromSeed.js';
|
||||
export { secp256k1Recover } from './recover.js';
|
||||
export { secp256k1Sign } from './sign.js';
|
||||
export { secp256k1PrivateKeyTweakAdd } from './tweakAdd.js';
|
||||
export { secp256k1Verify } from './verify.js';
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a, u8aToHex } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { mnemonicToMiniSecret } from '../../mnemonic/index.js';
|
||||
import { perfWasm } from '../../test/index.js';
|
||||
import { secp256k1PairFromSeed } from '../index.js';
|
||||
|
||||
// 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('secp256k1PairFromSeed', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
const TEST = hexToU8a('0x4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae');
|
||||
const RESULT = {
|
||||
publicKey: hexToU8a('0x03fd8c74f795ced92064b86191cb2772b1e3a0947740aa0a5a6e379592471fd85b'),
|
||||
secretKey: hexToU8a('0x4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae')
|
||||
};
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('generates a valid publicKey/secretKey pair (u8a)', (): void => {
|
||||
expect(secp256k1PairFromSeed(TEST, onlyJs)).toEqual(RESULT);
|
||||
});
|
||||
|
||||
tests.forEach(([mnemonic, secretKey, publicKey], index): void => {
|
||||
it(`creates valid against known (${index})`, (): void => {
|
||||
const seed = mnemonicToMiniSecret(mnemonic);
|
||||
const pair = secp256k1PairFromSeed(seed, onlyJs);
|
||||
|
||||
expect(u8aToHex(pair.secretKey)).toEqual(secretKey);
|
||||
expect(u8aToHex(pair.publicKey)).toEqual(publicKey);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
perfWasm('secp256k1PairFromSeed', 500, (input, onlyJs) =>
|
||||
secp256k1PairFromSeed(input, onlyJs)
|
||||
);
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||
|
||||
import { hasBigInt, u8aEmpty } from '@polkadot/util';
|
||||
import { isReady, secp256k1FromSeed } from '@polkadot/wasm-crypto';
|
||||
|
||||
/**
|
||||
* @name secp256k1PairFromSeed
|
||||
* @description Returns a object containing a `publicKey` & `secretKey` generated from the supplied seed.
|
||||
*/
|
||||
export function secp256k1PairFromSeed (seed: Uint8Array, onlyJs?: boolean): Keypair {
|
||||
if (seed.length !== 32) {
|
||||
throw new Error('Expected valid 32-byte private key as a seed');
|
||||
}
|
||||
|
||||
if (!hasBigInt || (!onlyJs && isReady())) {
|
||||
const full = secp256k1FromSeed(seed);
|
||||
const publicKey = full.slice(32);
|
||||
|
||||
// There is an issue with the secp256k1 when running in an ASM.js environment where
|
||||
// it seems that the lazy static section yields invalid results on the _first_ run.
|
||||
// If this happens, fail outright, we cannot allow invalid return values
|
||||
// https://github.com/polkadot-js/wasm/issues/307
|
||||
if (u8aEmpty(publicKey)) {
|
||||
throw new Error('Invalid publicKey generated from WASM interface');
|
||||
}
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
secretKey: full.slice(0, 32)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
publicKey: secp256k1.getPublicKey(seed, true),
|
||||
secretKey: seed
|
||||
};
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { u8aToHex, u8aToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { keccakAsU8a } from '../keccak/index.js';
|
||||
import { perfWasm } from '../test/index.js';
|
||||
import { secp256k1Recover } from './index.js';
|
||||
|
||||
const sig = u8aToU8a('0x7505f2880114da51b3f5d535f8687953c0ab9af4ab81e592eaebebf53b728d2b6dfd9b5bcd70fee412b1f31360e7c2774009305cb84fc50c1d0ff8034dfa5fff');
|
||||
const msg = u8aToU8a('0xa30b64ce1eedf409c8afb801d72c05234e64849ea538c15dd3c8cf4ffcf166c9');
|
||||
|
||||
describe('secp256k1Recover', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('recovers a publicKey', (): void => {
|
||||
const pubKey = '0x93a9fc7154c6da3c826415df01eb0e37fb4da4b0';
|
||||
const res = keccakAsU8a(secp256k1Recover(msg, sig, 0, undefined, onlyJs));
|
||||
|
||||
expect(u8aToHex(res.subarray(-20))).toEqual(pubKey);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
perfWasm('secp256k1Recover', 200, (_, onlyJs) =>
|
||||
secp256k1Recover(msg, sig, 0, undefined, onlyJs)
|
||||
);
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HashType } from './types.js';
|
||||
|
||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||
|
||||
import { hasBigInt, u8aToU8a } from '@polkadot/util';
|
||||
import { isReady, secp256k1Recover as wasm } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { secp256k1Compress } from './compress.js';
|
||||
import { secp256k1Expand } from './expand.js';
|
||||
|
||||
/**
|
||||
* @name secp256k1Recover
|
||||
* @description Recovers a publicKey from the supplied signature
|
||||
*/
|
||||
export function secp256k1Recover (msgHash: string | Uint8Array, signature: string | Uint8Array, recovery: number, hashType: HashType = 'blake2', onlyJs?: boolean): Uint8Array {
|
||||
const sig = u8aToU8a(signature).subarray(0, 64);
|
||||
const msg = u8aToU8a(msgHash);
|
||||
const publicKey = !hasBigInt || (!onlyJs && isReady())
|
||||
? wasm(msg, sig, recovery)
|
||||
: secp256k1.Signature
|
||||
.fromCompact(sig)
|
||||
.addRecoveryBit(recovery)
|
||||
.recoverPublicKey(msg)
|
||||
.toRawBytes();
|
||||
|
||||
if (!publicKey) {
|
||||
throw new Error('Unable to recover publicKey from signature');
|
||||
}
|
||||
|
||||
return hashType === 'keccak'
|
||||
? secp256k1Expand(publicKey, onlyJs)
|
||||
: secp256k1Compress(publicKey, onlyJs);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { perfWasm } from '../test/index.js';
|
||||
import { secp256k1PairFromSeed, secp256k1Sign } from './index.js';
|
||||
|
||||
const pair = secp256k1PairFromSeed(hexToU8a('0x4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae'));
|
||||
const msg = hexToU8a('0xa30b64ce1eedf409c8afb801d72c05234e64849ea538c15dd3c8cf4ffcf166c9');
|
||||
|
||||
describe('sign', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
for (const onlyJs of [false, true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('generates a known signature', (): void => {
|
||||
expect(
|
||||
secp256k1Sign(msg, pair, undefined, onlyJs)
|
||||
).toEqual(hexToU8a(
|
||||
// from elliptic, this is - 0xdf92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba8c4efa521361678d9b8582744d77aa4b8d886d7380b7808a683174afad9c4700300
|
||||
// libsecp256k1 & @noble/hashes do agree here...
|
||||
'0xdf92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba83b105adec9e9872647a7d8bb28855b45e22805aea3d097953cbb1391f671d13e01'
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// since the libsecp256k1 signatures don't match (but can be verified), we
|
||||
// do both signing and verification here (checking the extracted key)
|
||||
perfWasm('secp256k1Sign', 1000, (_, onlyJs) =>
|
||||
secp256k1Sign(msg, pair, undefined, onlyJs)
|
||||
);
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../types.js';
|
||||
import type { HashType } from './types.js';
|
||||
|
||||
import { secp256k1 } from '@noble/curves/secp256k1';
|
||||
|
||||
import { bnToU8a, hasBigInt, u8aConcat } from '@polkadot/util';
|
||||
import { isReady, secp256k1Sign as wasm } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { BN_BE_256_OPTS } from '../bn.js';
|
||||
import { hasher } from './hasher.js';
|
||||
|
||||
/**
|
||||
* @name secp256k1Sign
|
||||
* @description Returns message signature of `message`, using the supplied pair
|
||||
*/
|
||||
export function secp256k1Sign (message: Uint8Array | string, { secretKey }: Partial<Keypair>, hashType: HashType = 'blake2', onlyJs?: boolean): Uint8Array {
|
||||
if (secretKey?.length !== 32) {
|
||||
throw new Error('Expected valid secp256k1 secretKey, 32-bytes');
|
||||
}
|
||||
|
||||
const data = hasher(hashType, message, onlyJs);
|
||||
|
||||
if (!hasBigInt || (!onlyJs && isReady())) {
|
||||
return wasm(data, secretKey);
|
||||
}
|
||||
|
||||
const signature = secp256k1.sign(data, secretKey, { lowS: true });
|
||||
|
||||
return u8aConcat(
|
||||
bnToU8a(signature.r, BN_BE_256_OPTS),
|
||||
bnToU8a(signature.s, BN_BE_256_OPTS),
|
||||
new Uint8Array([signature.recovery || 0])
|
||||
);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import '../bundleInit.js';
|
||||
|
||||
import { stringToU8a } from '@polkadot/util';
|
||||
|
||||
import { randomAsU8a } from '../random/asU8a.js';
|
||||
import { hasher } from './hasher.js';
|
||||
import { secp256k1Expand, secp256k1PairFromSeed, secp256k1Sign, secp256k1Verify } from './index.js';
|
||||
|
||||
const MESSAGE = stringToU8a('this is a message');
|
||||
|
||||
describe('sign and verify', (): void => {
|
||||
it('verify message signature', (): void => {
|
||||
const address = '0x59f587c045d4d4e9aa1016eae43770fc0551df8a385027723342753a876aeef0';
|
||||
const sig = '0x92fcacf0946bbd10b31dfe16d567ed1d3014e81007dd9e5256e19c0f07eacc1643b151ca29e449a765e16a7ce59b88d800467d6b3412d30ea8ad22307a59664b00';
|
||||
const msg = stringToU8a('secp256k1');
|
||||
|
||||
expect(secp256k1Verify(msg, sig, address)).toEqual(true);
|
||||
});
|
||||
|
||||
it('has 65-byte signatures', (): void => {
|
||||
const pair = secp256k1PairFromSeed(randomAsU8a());
|
||||
|
||||
expect(secp256k1Sign(MESSAGE, pair)).toHaveLength(65);
|
||||
});
|
||||
|
||||
it('signs/verifies a message by random key (blake2)', (): void => {
|
||||
const pair = secp256k1PairFromSeed(randomAsU8a());
|
||||
const signature = secp256k1Sign(MESSAGE, pair);
|
||||
const address = hasher('blake2', pair.publicKey);
|
||||
|
||||
expect(secp256k1Verify(MESSAGE, signature, address)).toEqual(true);
|
||||
});
|
||||
|
||||
it('signs/verifies a message by random key (keccak)', (): void => {
|
||||
const pair = secp256k1PairFromSeed(randomAsU8a());
|
||||
const signature = secp256k1Sign(MESSAGE, pair, 'keccak');
|
||||
const address = hasher('keccak', secp256k1Expand(pair.publicKey));
|
||||
|
||||
expect(secp256k1Verify(MESSAGE, signature, address, 'keccak')).toEqual(true);
|
||||
});
|
||||
|
||||
it('fails verification on hasher mismatches', (): void => {
|
||||
const pair = secp256k1PairFromSeed(randomAsU8a());
|
||||
const signature = secp256k1Sign(MESSAGE, pair, 'keccak');
|
||||
const address = hasher('keccak', secp256k1Expand(pair.publicKey));
|
||||
|
||||
expect(secp256k1Verify(MESSAGE, signature, address, 'blake2')).toEqual(false);
|
||||
});
|
||||
|
||||
it('works over a range of random keys (blake2)', (): void => {
|
||||
for (let i = 0; i < 256; i++) {
|
||||
const pair = secp256k1PairFromSeed(randomAsU8a());
|
||||
|
||||
try {
|
||||
expect(
|
||||
secp256k1Verify(
|
||||
MESSAGE,
|
||||
secp256k1Sign(MESSAGE, pair, 'blake2'),
|
||||
hasher('blake2', pair.publicKey),
|
||||
'blake2'
|
||||
)
|
||||
).toEqual(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 = secp256k1PairFromSeed(randomAsU8a());
|
||||
|
||||
try {
|
||||
expect(
|
||||
secp256k1Verify(
|
||||
MESSAGE,
|
||||
secp256k1Sign(MESSAGE, pair, 'keccak'),
|
||||
hasher('keccak', secp256k1Expand(pair.publicKey)),
|
||||
'keccak'
|
||||
)
|
||||
).toEqual(true);
|
||||
} catch (error) {
|
||||
console.error(`keccak failed on #${i}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}, 120000);
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { secp256k1PrivateKeyTweakAdd } from './tweakAdd.js';
|
||||
|
||||
describe('TweakAdd', (): void => {
|
||||
it('fails for wrong array length', (): void => {
|
||||
const A = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);
|
||||
const B = new Uint8Array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]);
|
||||
|
||||
expect(
|
||||
() => secp256k1PrivateKeyTweakAdd(A, B)
|
||||
).toThrow(/Expected tweak to be an Uint8Array/);
|
||||
});
|
||||
|
||||
for (const onlyBn of [false, true]) {
|
||||
describe(`onlyBn=${(onlyBn && 'true') || 'false'}`, (): void => {
|
||||
it('succeeds for a simple case', (): void => {
|
||||
const A = new Uint8Array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]);
|
||||
const B = new Uint8Array([3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3]);
|
||||
|
||||
expect(
|
||||
secp256k1PrivateKeyTweakAdd(A, B, onlyBn)
|
||||
).toEqual(new Uint8Array([
|
||||
3, 4, 3, 4, 3, 4, 3, 4, 3,
|
||||
4, 3, 4, 3, 4, 3, 4, 3, 4,
|
||||
3, 4, 3, 4, 3, 4, 3, 4, 3,
|
||||
4, 3, 4, 3, 4
|
||||
]));
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { _0n, BN, bnToU8a, hasBigInt, isU8a, nToU8a, u8aToBigInt } from '@polkadot/util';
|
||||
import { BigInt } from '@polkadot/x-bigint';
|
||||
|
||||
import { BN_BE_256_OPTS, BN_BE_OPTS } from '../bn.js';
|
||||
|
||||
// pre-defined curve param as lifted form elliptic
|
||||
// https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/curves.js#L182
|
||||
const N = 'ffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141'.replace(/ /g, '');
|
||||
const N_BI = BigInt(`0x${N}`);
|
||||
const N_BN = new BN(N, 'hex');
|
||||
|
||||
function addBi (seckey: Uint8Array, tweak: Uint8Array): Uint8Array {
|
||||
let res = u8aToBigInt(tweak, BN_BE_OPTS);
|
||||
|
||||
if (res >= N_BI) {
|
||||
throw new Error('Tweak parameter is out of range');
|
||||
}
|
||||
|
||||
res += u8aToBigInt(seckey, BN_BE_OPTS);
|
||||
|
||||
if (res >= N_BI) {
|
||||
res -= N_BI;
|
||||
}
|
||||
|
||||
if (res === _0n) {
|
||||
throw new Error('Invalid resulting private key');
|
||||
}
|
||||
|
||||
return nToU8a(res, BN_BE_256_OPTS);
|
||||
}
|
||||
|
||||
function addBn (seckey: Uint8Array, tweak: Uint8Array): Uint8Array {
|
||||
const res = new BN(tweak);
|
||||
|
||||
if (res.cmp(N_BN) >= 0) {
|
||||
throw new Error('Tweak parameter is out of range');
|
||||
}
|
||||
|
||||
res.iadd(new BN(seckey));
|
||||
|
||||
if (res.cmp(N_BN) >= 0) {
|
||||
res.isub(N_BN);
|
||||
}
|
||||
|
||||
if (res.isZero()) {
|
||||
throw new Error('Invalid resulting private key');
|
||||
}
|
||||
|
||||
return bnToU8a(res, BN_BE_256_OPTS);
|
||||
}
|
||||
|
||||
export function secp256k1PrivateKeyTweakAdd (seckey: Uint8Array, tweak: Uint8Array, onlyBn?: boolean): Uint8Array {
|
||||
if (!isU8a(seckey) || seckey.length !== 32) {
|
||||
throw new Error('Expected seckey to be an Uint8Array with length 32');
|
||||
} else if (!isU8a(tweak) || tweak.length !== 32) {
|
||||
throw new Error('Expected tweak to be an Uint8Array with length 32');
|
||||
}
|
||||
|
||||
return !hasBigInt || onlyBn
|
||||
? addBn(seckey, tweak)
|
||||
: addBi(seckey, tweak);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export type HashType = 'blake2' | 'keccak';
|
||||
@@ -1,81 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import '../bundleInit.js';
|
||||
|
||||
import { hexToU8a } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { perfWasm } from '../test/index.js';
|
||||
import { hasher } from './hasher.js';
|
||||
import { secp256k1PairFromSeed, secp256k1Verify } from './index.js';
|
||||
|
||||
const message = 'Pay KSMs to the Kusama account:88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee';
|
||||
|
||||
describe('secp256k1Verify', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
for (const onlyJs of [true]) {
|
||||
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
|
||||
it('validates known ETH against address', (): void => {
|
||||
expect(
|
||||
secp256k1Verify(
|
||||
`\x19Ethereum Signed Message:\n${message.length.toString()}${message}`,
|
||||
'0x55bd020bdbbdc02de34e915effc9b18a99002f4c29f64e22e8dcbb69e722ea6c28e1bb53b9484063fbbfd205e49dcc1f620929f520c9c4c3695150f05a28f52a01',
|
||||
'0x002309df96687e44280bb72c3818358faeeb699c',
|
||||
'keccak',
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
for (const isPublic of [false, true]) {
|
||||
describe(`validation against known, isPublic=${(isPublic && 'true') || 'false'}`, (): void => {
|
||||
const pair = secp256k1PairFromSeed(hexToU8a('0x4380de832af797688026ce24f85204d508243f201650c1a134929e5458b7fbae'));
|
||||
const msg = hexToU8a('0xa30b64ce1eedf409c8afb801d72c05234e64849ea538c15dd3c8cf4ffcf166c9');
|
||||
const addr = isPublic
|
||||
? pair.publicKey
|
||||
: hasher('blake2', pair.publicKey, onlyJs);
|
||||
|
||||
it('signature from JS', (): void => {
|
||||
expect(
|
||||
secp256k1Verify(
|
||||
msg,
|
||||
'0xdf92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba8c4efa521361678d9b8582744d77aa4b8d886d7380b7808a683174afad9c4700300',
|
||||
addr,
|
||||
'blake2',
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
it('signature from wasm', (): void => {
|
||||
expect(
|
||||
secp256k1Verify(
|
||||
msg,
|
||||
'0xdf92f73d9f060cefacf187b5414491cb992998ace017fa48839b5cda3e264ba83b105adec9e9872647a7d8bb28855b45e22805aea3d097953cbb1391f671d13e01',
|
||||
addr,
|
||||
'blake2',
|
||||
onlyJs
|
||||
)
|
||||
).toEqual(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
perfWasm('secp256k1Verify', 100, (_, onlyJs) =>
|
||||
secp256k1Verify(
|
||||
`\x19Ethereum Signed Message:\n${message.length.toString()}${message}`,
|
||||
'0x55bd020bdbbdc02de34e915effc9b18a99002f4c29f64e22e8dcbb69e722ea6c28e1bb53b9484063fbbfd205e49dcc1f620929f520c9c4c3695150f05a28f52a01',
|
||||
'0x002309df96687e44280bb72c3818358faeeb699c',
|
||||
'keccak',
|
||||
onlyJs
|
||||
)
|
||||
);
|
||||
});
|
||||
@@ -1,32 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HashType } from './types.js';
|
||||
|
||||
import { u8aEq, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
import { hasher } from './hasher.js';
|
||||
import { secp256k1Recover } from './recover.js';
|
||||
|
||||
/**
|
||||
* @name secp256k1Verify
|
||||
* @description Verifies the signature of `message`, using the supplied pair
|
||||
*/
|
||||
export function secp256k1Verify (msgHash: string | Uint8Array, signature: string | Uint8Array, address: string | Uint8Array, hashType: HashType = 'blake2', onlyJs?: boolean): boolean {
|
||||
const sig = u8aToU8a(signature);
|
||||
|
||||
if (sig.length !== 65) {
|
||||
throw new Error(`Expected signature with 65 bytes, ${sig.length} found instead`);
|
||||
}
|
||||
|
||||
const publicKey = secp256k1Recover(hasher(hashType, msgHash), sig, sig[64], hashType, onlyJs);
|
||||
const signerAddr = hasher(hashType, publicKey, onlyJs);
|
||||
const inputAddr = u8aToU8a(address);
|
||||
|
||||
// for Ethereum (keccak) the last 20 bytes is the address
|
||||
return u8aEq(publicKey, inputAddr) || (
|
||||
hashType === 'keccak'
|
||||
? u8aEq(signerAddr.slice(-20), inputAddr.slice(-20))
|
||||
: u8aEq(signerAddr, inputAddr)
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/**
|
||||
* @summary Utilities for working with signatures
|
||||
*/
|
||||
|
||||
export { signatureVerify } from './verify.js';
|
||||
@@ -1,230 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { hexToU8a, stringToU8a, u8aConcat, u8aToHex, u8aWrapBytes } from '@polkadot/util';
|
||||
import { waitReady } from '@polkadot/wasm-crypto';
|
||||
|
||||
import { decodeAddress } from '../address/index.js';
|
||||
import { secp256k1Sign } from '../secp256k1/sign.js';
|
||||
import { signatureVerify } from './index.js';
|
||||
|
||||
const ADDR_ED = 'DxN4uvzwPzJLtn17yew6jEffPhXQfdKHTp2brufb98vGbPN';
|
||||
const ADDR_SR = 'EK1bFgKm2FsghcttHT7TB7rNyXApFgs9fCbijMGQNyFGBQm';
|
||||
const ADDR_SR_WRAP = 'J9nD3s7zssCX7bion1xctAF6xcVexcpy2uwy4jTm9JL8yuK';
|
||||
const ADDR_EC = 'XyFVXiGaHxoBhXZkSh6NS2rjFyVaVNUo5UiZDqZbuSfUdji';
|
||||
const ADDR_ET = '0x54Dab85EE2c7b9F7421100d7134eFb5DfA4239bF';
|
||||
const MESSAGE = 'hello world';
|
||||
const SIG_ED = '0x299d3bf4c8bb51af732f8067b3a3015c0862a5ff34721749d8ed6577ea2708365d1c5f76bd519009971e41156f12c70abc2533837ceb3bad9a05a99ab923de06';
|
||||
const SIG_SR = '0xca01419b5a17219f7b78335658cab3b126db523a5df7be4bfc2bef76c2eb3b1dcf4ca86eb877d0a6cf6df12db5995c51d13b00e005d053b892bd09c594434288';
|
||||
const SIG_SR_WRAP = '0x84b6afb1c8e54bbcb3f4872baf172580e21310e9387a53742627d6652d121447fa406b82805ed3184fb7bd519175cc9f99f283f97954d95cf966ee164df85489';
|
||||
const SIG_EC = '0x994638ee586d2c5dbd9bacacbc35d9b7e9018de8f7892f00c900db63bc57b1283e2ee7bc51a9b1c1dae121ac4f4b9e2a41cd1d6bf4bb3e24d7fed6faf6d85e0501';
|
||||
const SIG_ET = '0x4e35aad35793b71f08566615661c9b741d7c605bc8935ac08608dff685324d71b5704fbd14c9297d2f584ea0735f015dcf0def66b802b3f555e1db916eda4b7700';
|
||||
const MUL_ED = u8aToHex(u8aConcat(new Uint8Array([0]), hexToU8a(SIG_ED)));
|
||||
const MUL_SR = u8aToHex(u8aConcat(new Uint8Array([1]), hexToU8a(SIG_SR)));
|
||||
const MUL_EC = u8aToHex(u8aConcat(new Uint8Array([2]), hexToU8a(SIG_EC)));
|
||||
const MUL_ET = u8aToHex(u8aConcat(new Uint8Array([2]), hexToU8a(SIG_ET)));
|
||||
|
||||
describe('signatureVerify', (): void => {
|
||||
beforeEach(async (): Promise<void> => {
|
||||
await waitReady();
|
||||
});
|
||||
|
||||
it('throws on invalid signature length', (): void => {
|
||||
expect(
|
||||
() => signatureVerify(MESSAGE, new Uint8Array(32), ADDR_ED)
|
||||
).toThrow('Invalid signature length, expected [64..66] bytes, found 32');
|
||||
});
|
||||
|
||||
describe('verifyDetect', (): void => {
|
||||
it('verifies ed25519 signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_ED, ADDR_ED)).toEqual({
|
||||
crypto: 'ed25519',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_ED)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies ecdsa signatures', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_EC, ADDR_EC)).toEqual({
|
||||
crypto: 'ecdsa',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_EC)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an ethereum signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_ET, ADDR_ET)).toEqual({
|
||||
crypto: 'ethereum',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: hexToU8a(ADDR_ET)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an ethereum signature (known)', (): void => {
|
||||
const message = 'Pay KSMs to the Kusama account:88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee';
|
||||
|
||||
expect(signatureVerify(
|
||||
`\x19Ethereum Signed Message:\n${message.length.toString()}${message}`,
|
||||
'0x55bd020bdbbdc02de34e915effc9b18a99002f4c29f64e22e8dcbb69e722ea6c28e1bb53b9484063fbbfd205e49dcc1f620929f520c9c4c3695150f05a28f52a01',
|
||||
'0x002309df96687e44280bb72c3818358faeeb699c'
|
||||
)).toEqual({
|
||||
crypto: 'ethereum',
|
||||
isValid: true,
|
||||
isWrapped: true,
|
||||
publicKey: hexToU8a('0x002309df96687e44280bb72c3818358faeeb699c')
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on invalid ethereum signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_EC, ADDR_ET)).toEqual({
|
||||
crypto: 'none',
|
||||
isValid: false,
|
||||
isWrapped: false,
|
||||
publicKey: hexToU8a(ADDR_ET)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an sr25519 signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_SR, ADDR_SR)).toEqual({
|
||||
crypto: 'sr25519',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_SR)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an sr25519 signature (with msg wrapper, without wrapped sig)', (): void => {
|
||||
expect(signatureVerify(u8aWrapBytes(MESSAGE), SIG_SR_WRAP, ADDR_SR_WRAP)).toEqual({
|
||||
crypto: 'sr25519',
|
||||
isValid: true,
|
||||
isWrapped: true,
|
||||
publicKey: decodeAddress(ADDR_SR_WRAP)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an sr25519 signature (without msg wrapper, with wrapped sig)', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_SR_WRAP, ADDR_SR_WRAP)).toEqual({
|
||||
crypto: 'sr25519',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_SR_WRAP)
|
||||
});
|
||||
});
|
||||
|
||||
it('allows various inputs', (): void => {
|
||||
expect(signatureVerify(stringToU8a(MESSAGE), hexToU8a(SIG_ED), decodeAddress(ADDR_ED))).toEqual({
|
||||
crypto: 'ed25519',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_ED)
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on an invalid signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, SIG_SR, ADDR_ED)).toEqual({
|
||||
crypto: 'none',
|
||||
isValid: false,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_ED)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyMultisig', (): void => {
|
||||
it('verifies an ed25519 signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, MUL_ED, ADDR_ED)).toEqual({
|
||||
crypto: 'ed25519',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_ED)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an ecdsa signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, MUL_EC, ADDR_EC)).toEqual({
|
||||
crypto: 'ecdsa',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_EC)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an ethereum signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, MUL_ET, ADDR_ET)).toEqual({
|
||||
crypto: 'ethereum',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: hexToU8a(ADDR_ET)
|
||||
});
|
||||
});
|
||||
|
||||
it('verifies an sr25519 signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, MUL_SR, ADDR_SR)).toEqual({
|
||||
crypto: 'sr25519',
|
||||
isValid: true,
|
||||
isWrapped: false,
|
||||
publicKey: decodeAddress(ADDR_SR)
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on an invalid signature', (): void => {
|
||||
expect(signatureVerify(MESSAGE, MUL_SR, ADDR_ED)).toEqual({
|
||||
crypto: 'none',
|
||||
isValid: false,
|
||||
isWrapped: false,
|
||||
publicKey: new Uint8Array([61, 12, 55, 211, 0, 211, 97, 199, 4, 37, 17, 213, 81, 175, 166, 23, 251, 199, 144, 210, 19, 83, 186, 1, 196, 231, 14, 156, 171, 46, 141, 146])
|
||||
});
|
||||
});
|
||||
/**
|
||||
* ref: https://github.com/polkadot-js/common/issues/1898
|
||||
*
|
||||
* The following test ensures that we cover a reproduction that showed
|
||||
* an inherent issue with verifying ecdsa signatures which is fixed in
|
||||
* https://github.com/polkadot-js/common/pull/1973.
|
||||
*
|
||||
* It uses a random secretKey, and publicKey pair along with `secp256k1Sign`
|
||||
* as the signer which is used for `ecdsa`.
|
||||
*/
|
||||
it('Ensure ecdsa can sign and verify 1000 messages', (): void => {
|
||||
const verifyThousandMessages = () => {
|
||||
const secretKey = new Uint8Array([
|
||||
103, 97, 114, 98, 97, 103, 101, 32, 114, 105, 100,
|
||||
103, 101, 32, 107, 105, 99, 107, 32, 114, 111, 115,
|
||||
101, 32, 101, 110, 100, 32, 115, 113, 117, 101
|
||||
]);
|
||||
const publicKey = new Uint8Array([
|
||||
2, 179, 102, 92, 246, 50, 172, 88,
|
||||
81, 116, 8, 211, 192, 131, 154, 184,
|
||||
122, 83, 180, 104, 4, 227, 214, 195,
|
||||
140, 11, 82, 229, 49, 211, 185, 176,
|
||||
63
|
||||
]);
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const message = `message ${i}`;
|
||||
const encodedMessage = stringToU8a(message);
|
||||
const signature = secp256k1Sign(encodedMessage, { secretKey });
|
||||
|
||||
const { isValid: valid } = signatureVerify(
|
||||
message,
|
||||
signature,
|
||||
publicKey
|
||||
);
|
||||
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
expect(verifyThousandMessages()).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { KeypairType, VerifyResult } from '../types.js';
|
||||
|
||||
import { u8aIsWrapped, u8aToU8a, u8aUnwrapBytes, u8aWrapBytes } from '@polkadot/util';
|
||||
|
||||
import { decodeAddress } from '../address/decode.js';
|
||||
import { ed25519Verify } from '../ed25519/verify.js';
|
||||
import { secp256k1Verify } from '../secp256k1/verify.js';
|
||||
import { sr25519Verify } from '../sr25519/verify.js';
|
||||
|
||||
interface VerifyInput {
|
||||
message: Uint8Array;
|
||||
publicKey: Uint8Array;
|
||||
signature: Uint8Array;
|
||||
}
|
||||
|
||||
type Verifier = [KeypairType, (message: Uint8Array | string, signature: Uint8Array, publicKey: Uint8Array) => boolean];
|
||||
|
||||
type VerifyFn = (result: VerifyResult, input: VerifyInput) => VerifyResult;
|
||||
|
||||
const secp256k1VerifyHasher = (hashType: 'blake2' | 'keccak') =>
|
||||
(message: Uint8Array | string, signature: Uint8Array, publicKey: Uint8Array) =>
|
||||
secp256k1Verify(message, signature, publicKey, hashType, true);
|
||||
|
||||
const VERIFIERS_ECDSA: Verifier[] = [
|
||||
['ecdsa', secp256k1VerifyHasher('blake2')],
|
||||
['ethereum', secp256k1VerifyHasher('keccak')]
|
||||
];
|
||||
|
||||
const VERIFIERS: Verifier[] = [
|
||||
['ed25519', ed25519Verify],
|
||||
['sr25519', sr25519Verify]
|
||||
];
|
||||
|
||||
function verifyDetect (result: VerifyResult, { message, publicKey, signature }: VerifyInput, verifiers = [...VERIFIERS, ...VERIFIERS_ECDSA]): VerifyResult {
|
||||
result.isValid = verifiers.some(([crypto, verify]): boolean => {
|
||||
try {
|
||||
if (verify(message, signature, publicKey)) {
|
||||
result.crypto = crypto;
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// do nothing, result.isValid still set to false
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function verifyMultisig (result: VerifyResult, { message, publicKey, signature }: VerifyInput): VerifyResult {
|
||||
if (![0, 1, 2].includes(signature[0]) || ![65, 66].includes(signature.length)) {
|
||||
throw new Error(`Unknown crypto type, expected signature prefix [0..2], found ${signature[0]}`);
|
||||
}
|
||||
|
||||
// If the signature is 66 bytes it must be an ecdsa signature
|
||||
// containing: prefix [1 byte] + signature [65] bytes.
|
||||
// Remove the and then verify
|
||||
if (signature.length === 66) {
|
||||
result = verifyDetect(result, { message, publicKey, signature: signature.subarray(1) }, VERIFIERS_ECDSA);
|
||||
} else {
|
||||
// The signature contains 65 bytes which is either
|
||||
// - A ed25519 or sr25519 signature [1 byte prefix + 64 bytes]
|
||||
// - An ecdsa signature [65 bytes]
|
||||
result = verifyDetect(result, { message, publicKey, signature: signature.subarray(1) }, VERIFIERS);
|
||||
|
||||
if (!result.isValid) {
|
||||
result = verifyDetect(result, { message, publicKey, signature }, VERIFIERS_ECDSA);
|
||||
}
|
||||
|
||||
// If both failed, explicitly set crypto to 'none'
|
||||
if (!result.isValid) {
|
||||
result.crypto = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getVerifyFn (signature: Uint8Array): VerifyFn {
|
||||
return [0, 1, 2].includes(signature[0]) && [65, 66].includes(signature.length)
|
||||
? verifyMultisig
|
||||
: verifyDetect;
|
||||
}
|
||||
|
||||
export function signatureVerify (message: string | Uint8Array, signature: string | Uint8Array, addressOrPublicKey: string | Uint8Array): VerifyResult {
|
||||
const signatureU8a = u8aToU8a(signature);
|
||||
|
||||
if (![64, 65, 66].includes(signatureU8a.length)) {
|
||||
throw new Error(`Invalid signature length, expected [64..66] bytes, found ${signatureU8a.length}`);
|
||||
}
|
||||
|
||||
const publicKey = decodeAddress(addressOrPublicKey);
|
||||
const input = { message: u8aToU8a(message), publicKey, signature: signatureU8a };
|
||||
const result: VerifyResult = { crypto: 'none', isValid: false, isWrapped: u8aIsWrapped(input.message, true), publicKey };
|
||||
const isWrappedBytes = u8aIsWrapped(input.message, false);
|
||||
const verifyFn = getVerifyFn(signatureU8a);
|
||||
|
||||
verifyFn(result, input);
|
||||
|
||||
if (result.crypto !== 'none' || (result.isWrapped && !isWrappedBytes)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
input.message = isWrappedBytes
|
||||
? u8aUnwrapBytes(input.message)
|
||||
: u8aWrapBytes(input.message);
|
||||
|
||||
return verifyFn(result, input);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import type { Keypair } from '../types.js';
|
||||
|
||||
import { u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { sr25519Agreement, sr25519PairFromSeed } from './index.js';
|
||||
|
||||
describe('agreement', (): void => {
|
||||
let pairA: Keypair;
|
||||
let pairB: Keypair;
|
||||
|
||||
beforeEach((): void => {
|
||||
pairA = sr25519PairFromSeed('0x98b3d305d5a5eace562387e47e59badd4d77e3f72cabfb10a60f8a197059f0a8');
|
||||
pairB = sr25519PairFromSeed('0x9732eea001851ff862d949a1699c9971f3a26edbede2ad7922cbbe9a0701f366');
|
||||
});
|
||||
|
||||
it('matches a known agreement (both ways)', (): void => {
|
||||
const TEST = '0xb03a0b198c34c16f35cae933d88b16341b4cef3e84e851f20e664c6a30527f4e';
|
||||
|
||||
expect(
|
||||
u8aToHex(sr25519Agreement(pairA.secretKey, pairB.publicKey))
|
||||
).toEqual(TEST);
|
||||
expect(
|
||||
u8aToHex(sr25519Agreement(pairB.secretKey, pairA.publicKey))
|
||||
).toEqual(TEST);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { getSharedSecret } from '@scure/sr25519';
|
||||
|
||||
import { u8aToU8a } from '@polkadot/util';
|
||||
|
||||
/**
|
||||
* @name sr25519Agreement
|
||||
* @description Key agreement between other's public key and self secret key
|
||||
*/
|
||||
export function sr25519Agreement (secretKey: string | Uint8Array, publicKey: string | Uint8Array): Uint8Array {
|
||||
const secretKeyU8a = u8aToU8a(secretKey);
|
||||
const publicKeyU8a = u8aToU8a(publicKey);
|
||||
|
||||
if (publicKeyU8a.length !== 32) {
|
||||
throw new Error(`Invalid publicKey, received ${publicKeyU8a.length} bytes, expected 32`);
|
||||
} else if (secretKeyU8a.length !== 64) {
|
||||
throw new Error(`Invalid secretKey, received ${secretKeyU8a.length} bytes, expected 64`);
|
||||
}
|
||||
|
||||
return getSharedSecret(secretKeyU8a, publicKeyU8a);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../types.js';
|
||||
|
||||
import * as sr25519 from '@scure/sr25519';
|
||||
|
||||
import { isU8a } from '@polkadot/util';
|
||||
|
||||
export function createDeriveFn (derive: (pair: Uint8Array, cc: Uint8Array) => Uint8Array): (keypair: Keypair, chainCode: Uint8Array) => Keypair {
|
||||
return (keypair: Keypair, chainCode: Uint8Array): Keypair => {
|
||||
if (!isU8a(chainCode) || chainCode.length !== 32) {
|
||||
throw new Error('Invalid chainCode passed to derive');
|
||||
}
|
||||
|
||||
const secretKey = derive(keypair.secretKey, chainCode);
|
||||
const publicKey = sr25519.getPublicKey(secretKey);
|
||||
|
||||
return { publicKey, secretKey };
|
||||
};
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as sr25519 from '@scure/sr25519';
|
||||
|
||||
import { createDeriveFn } from './derive.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
export const sr25519DeriveHard = /*#__PURE__*/ createDeriveFn(sr25519.HDKD.secretHard);
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as sr25519 from '@scure/sr25519';
|
||||
|
||||
import { isU8a, u8aToU8a } from '@polkadot/util';
|
||||
|
||||
export function sr25519DerivePublic (publicKey: string | Uint8Array, chainCode: Uint8Array): Uint8Array {
|
||||
const publicKeyU8a = u8aToU8a(publicKey);
|
||||
|
||||
if (!isU8a(chainCode) || chainCode.length !== 32) {
|
||||
throw new Error('Invalid chainCode passed to derive');
|
||||
} else if (publicKeyU8a.length !== 32) {
|
||||
throw new Error(`Invalid publicKey, received ${publicKeyU8a.length} bytes, expected 32`);
|
||||
}
|
||||
|
||||
return sr25519.HDKD.publicSoft(publicKeyU8a, chainCode);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import * as sr25519 from '@scure/sr25519';
|
||||
|
||||
import { createDeriveFn } from './derive.js';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
export const sr25519DeriveSoft = /*#__PURE__*/ createDeriveFn(sr25519.HDKD.secretSoft);
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { sr25519Agreement } from './agreement.js';
|
||||
export { sr25519DeriveHard } from './deriveHard.js';
|
||||
export { sr25519DerivePublic } from './derivePublic.js';
|
||||
export { sr25519DeriveSoft } from './deriveSoft.js';
|
||||
export { sr25519PairFromSeed } from './pair/fromSeed.js';
|
||||
export { sr25519Sign } from './sign.js';
|
||||
export { sr25519Verify } from './verify.js';
|
||||
export { sr25519VrfSign } from './vrfSign.js';
|
||||
export { sr25519VrfVerify } from './vrfVerify.js';
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/// <reference types="@polkadot/dev-test/globals.d.ts" />
|
||||
|
||||
import { stringToU8a, u8aToHex } from '@polkadot/util';
|
||||
|
||||
import { mnemonicToMiniSecret } from '../../mnemonic/index.js';
|
||||
import { sr25519PairFromSeed } from '../index.js';
|
||||
import tests from './testing.spec.js';
|
||||
|
||||
describe('sr25519PairFromSeed', (): void => {
|
||||
const TEST = stringToU8a('12345678901234567890123456789012');
|
||||
const RESULT = {
|
||||
publicKey: 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]),
|
||||
secretKey: new Uint8Array([240, 16, 102, 96, 195, 221, 162, 63, 22, 218, 169, 172, 91, 129, 27, 150, 48, 119, 245, 188, 10, 248, 159, 133, 128, 79, 13, 232, 228, 36, 240, 80, 249, 141, 102, 243, 148, 66, 80, 111, 249, 71, 253, 145, 31, 24, 199, 167, 165, 218, 99, 154, 99, 232, 211, 180, 226, 51, 247, 65, 67, 217, 81, 193])
|
||||
};
|
||||
|
||||
it('generates a valid publicKey/secretKey pair (u8a)', (): void => {
|
||||
expect(
|
||||
sr25519PairFromSeed(TEST)
|
||||
).toEqual(RESULT);
|
||||
});
|
||||
|
||||
tests.forEach(([mnemonic, , , secret], index): void => {
|
||||
it(`creates valid against known (${index})`, (): void => {
|
||||
const seed = mnemonicToMiniSecret(mnemonic, 'Substrate');
|
||||
const pair = sr25519PairFromSeed(seed);
|
||||
|
||||
expect(
|
||||
u8aToHex(pair.secretKey)
|
||||
).toEqual(secret);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import * as sr25519 from '@scure/sr25519';
|
||||
|
||||
import { u8aToU8a } from '@polkadot/util';
|
||||
|
||||
/**
|
||||
* @name sr25519PairFromSeed
|
||||
* @description Returns a object containing a `publicKey` & `secretKey` generated from the supplied seed.
|
||||
*/
|
||||
export function sr25519PairFromSeed (seed: string | Uint8Array): Keypair {
|
||||
const seedU8a = u8aToU8a(seed);
|
||||
|
||||
if (seedU8a.length !== 32) {
|
||||
throw new Error(`Expected a seed matching 32 bytes, found ${seedU8a.length}`);
|
||||
}
|
||||
|
||||
const sec = sr25519.secretFromSeed(seedU8a);
|
||||
const pub = sr25519.getPublicKey(sec);
|
||||
|
||||
return {
|
||||
publicKey: pub,
|
||||
secretKey: sec
|
||||
};
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { u8aToU8a } from '@polkadot/util';
|
||||
|
||||
const SEC_LEN = 64;
|
||||
const PUB_LEN = 32;
|
||||
const TOT_LEN = SEC_LEN + PUB_LEN;
|
||||
|
||||
export function sr25519PairFromU8a (full: string | Uint8Array): Keypair {
|
||||
const fullU8a = u8aToU8a(full);
|
||||
|
||||
if (fullU8a.length !== TOT_LEN) {
|
||||
throw new Error(`Expected keypair with ${TOT_LEN} bytes, found ${fullU8a.length}`);
|
||||
}
|
||||
|
||||
return {
|
||||
publicKey: fullU8a.slice(SEC_LEN, TOT_LEN),
|
||||
secretKey: fullU8a.slice(0, SEC_LEN)
|
||||
};
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// Copyright 2017-2026 @polkadot/util-crypto authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Keypair } from '../../types.js';
|
||||
|
||||
import { u8aConcat } from '@polkadot/util';
|
||||
|
||||
export function sr25519KeypairToU8a ({ publicKey, secretKey }: Keypair): Uint8Array {
|
||||
return u8aConcat(secretKey, publicKey).slice();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user