Initial rebrand: @polkadot -> @pezkuwi (14 packages)

- Package namespace: @polkadot/* -> @pezkuwi/*
- Repository: polkadot-js/common -> pezkuwichain/pezkuwi-common
- Author: Pezkuwi Team <team@pezkuwichain.io>

Core packages:
- @pezkuwi/util (utilities)
- @pezkuwi/util-crypto (crypto primitives)
- @pezkuwi/keyring (account management)
- @pezkuwi/networks (chain metadata)
- @pezkuwi/hw-ledger (Ledger hardware wallet)
- @pezkuwi/x-* (10 polyfill packages)

Total: 14 packages
Upstream: polkadot-js/common v14.0.1
This commit is contained in:
2026-01-05 14:00:34 +03:00
commit ec06da0ebc
687 changed files with 48096 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
# @pezkuwi/util-crypto
Various useful cyrpto utility functions that are used across all projects in the [@pezkuwi](https://pezkuwi.js.org) namespace. It provides utility functions with additional safety checks, allowing not only for consistent coding, but also reducing the general boilerplate.
## Usage
Installation -
```
yarn add @pezkuwi/util-crypto
```
Functions can be imported as follows:
```js
import { mnemonicGenerate } from '@pezkuwi/util-crypto';
```
+45
View File
@@ -0,0 +1,45 @@
{
"author": "Jaco Greeff <jacogr@gmail.com>",
"bugs": "https://github.com/pezkuwichain/pezkuwi-common/issues",
"description": "A collection of useful crypto utilities for @pezkuwi",
"engines": {
"node": ">=18"
},
"homepage": "https://github.com/pezkuwichain/pezkuwi-common/tree/master/packages/util-crypto#readme",
"license": "Apache-2.0",
"name": "@pezkuwi/util-crypto",
"repository": {
"directory": "packages/util-crypto",
"type": "git",
"url": "https://github.com/pezkuwichain/pezkuwi-common.git"
},
"sideEffects": [
"./bundleInit.js",
"./bundleInit.cjs",
"./packageDetect.js",
"./packageDetect.cjs"
],
"type": "module",
"version": "14.0.1",
"browser": {
"crypto": false,
"stream": false
},
"main": "index.js",
"dependencies": {
"@noble/curves": "^1.3.0",
"@noble/hashes": "^1.3.3",
"@pezkuwi/networks": "14.0.1",
"@pezkuwi/util": "14.0.1",
"@pezkuwi/wasm-crypto": "^7.5.3",
"@pezkuwi/wasm-util": "^7.5.3",
"@pezkuwi/x-bigint": "14.0.1",
"@pezkuwi/x-randomvalues": "14.0.1",
"@scure/base": "^1.1.7",
"@scure/sr25519": "^0.2.0",
"tslib": "^2.8.0"
},
"peerDependencies": {
"@pezkuwi/util": "14.0.1"
}
}
@@ -0,0 +1,16 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexToU8a } from '@pezkuwi/util';
import { addressToEvm } from './addressToEvm.js';
describe('addressToEvm', (): void => {
it('creates a valid known EVM address', (): void => {
expect(
addressToEvm('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou')
).toEqual(hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee9'));
});
});
@@ -0,0 +1,12 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { decodeAddress } from './decode.js';
/**
* @name addressToEvm
* @summary Converts an SS58 address to its corresponding EVM address.
*/
export function addressToEvm (address: string | Uint8Array, ignoreChecksum?: boolean): Uint8Array {
return decodeAddress(address, ignoreChecksum).subarray(0, 20);
}
@@ -0,0 +1,44 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { checkAddress } from './index.js';
describe('checkAddress', (): void => {
it('returns [true, null] for Kusama', (): void => {
expect(
checkAddress('FJaco77EJ99VtBmVFibuBJR3x5Qq9KQrgQJvWjqScCcCCae', 2)
).toEqual([true, null]);
});
it('returns [true, null] for Substrate', (): void => {
expect(
checkAddress('5EnxxUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG', 42)
).toEqual([true, null]);
});
it('fails when an invalid base58 character is supplied', (): void => {
expect(
checkAddress('5EnxIUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG', 2)
).toEqual([false, 'Invalid base58 character "I" (0x49) at index 4']);
});
it('fails with invalid prefix when checking Substrate against Kusama prefix', (): void => {
expect(
checkAddress('5EnxxUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG', 2)
).toEqual([false, 'Prefix mismatch, expected 2, found 42']);
});
it('fails with invalid length when some bytes are missing', (): void => {
expect(
checkAddress('y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b', 42)
).toEqual([false, 'Invalid decoded address length']);
});
it('fails with invalid length on checksum mismatch', (): void => {
expect(
checkAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU', 42)
).toEqual([false, 'Invalid decoded address checksum']);
});
});
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
import { base58Decode } from '../base58/index.js';
import { checkAddressChecksum } from './checksum.js';
import { defaults } from './defaults.js';
/**
* @name checkAddress
* @summary Validates an ss58 address.
* @description
* From the provided input, validate that the address is a valid input.
*/
export function checkAddress (address: string, prefix: Prefix): [boolean, string | null] {
let decoded;
try {
decoded = base58Decode(address);
} catch (error) {
return [false, (error as Error).message];
}
const [isValid,,, ss58Decoded] = checkAddressChecksum(decoded);
if (ss58Decoded !== prefix) {
return [false, `Prefix mismatch, expected ${prefix}, found ${ss58Decoded}`];
} else if (!defaults.allowedEncodedLengths.includes(decoded.length)) {
return [false, 'Invalid decoded address length'];
}
return [isValid, isValid ? null : 'Invalid decoded address checksum'];
}
@@ -0,0 +1,45 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base58Decode } from '../base58/index.js';
import { checkAddressChecksum } from './checksum.js';
describe('checkAddressChecksum', (): void => {
it('correctly extracts the info from a 1-byte-prefix address', (): void => {
expect(
checkAddressChecksum(base58Decode('F3opxRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29'))
).toEqual([true, 33, 1, 2]);
});
it('correctly extracts the info from a 2-byte-prefix address (66)', (): void => {
expect(
checkAddressChecksum(base58Decode('cTGShekJ1L1UKFZR9xmv9UTJod7vqjFAPo4sDhXih2c3y1yLS'))
).toEqual([true, 34, 2, 66]);
});
it('correctly extracts the info from a 2-byte-prefix address (69)', (): void => {
expect(
checkAddressChecksum(base58Decode('cnVvyMzRdqjwejTFuByQQ4w2yu78V2hpFixjHQz5zr6NSYsxA'))
).toEqual([true, 34, 2, 69]);
});
it('correctly extracts the info from a 2-byte-prefix address (252)', (): void => {
expect(
checkAddressChecksum(base58Decode('xw8Ffc2SZtDqUJKd9Ky4vc7PRz2D2asuVkEEzf3WGAbw9cnfq'))
).toEqual([true, 34, 2, 252]);
});
it('correctly extracts the info from a 2-byte-prefix address (255)', (): void => {
expect(
checkAddressChecksum(base58Decode('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k'))
).toEqual([true, 34, 2, 255]);
});
it('correctly extracts the info from a 2-byte-prefix address (ecdsa, from Substrate)', (): void => {
expect(
checkAddressChecksum(base58Decode('4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV'))
).toEqual([true, 35, 2, 200]);
});
});
@@ -0,0 +1,25 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { sshash } from './sshash.js';
export function checkAddressChecksum (decoded: Uint8Array): [boolean, number, number, number] {
const ss58Length = (decoded[0] & 0b0100_0000) ? 2 : 1;
const ss58Decoded = ss58Length === 1
? decoded[0]
: ((decoded[0] & 0b0011_1111) << 2) | (decoded[1] >> 6) | ((decoded[1] & 0b0011_1111) << 8);
// 32/33 bytes public + 2 bytes checksum + prefix
const isPublicKey = [34 + ss58Length, 35 + ss58Length].includes(decoded.length);
const length = decoded.length - (isPublicKey ? 2 : 1);
// calculate the hash and do the checksum byte checks
const hash = sshash(decoded.subarray(0, length));
const isValid = (decoded[0] & 0b1000_0000) === 0 && ![46, 47].includes(decoded[0]) && (
isPublicKey
? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1]
: decoded[decoded.length - 1] === hash[0]
);
return [isValid, length, ss58Length, ss58Decoded];
}
@@ -0,0 +1,138 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexToU8a, stringToU8a, u8aToHex } from '@pezkuwi/util';
import { ALICE_PUBLIC_SR } from './encode.spec.js';
import { decodeAddress } from './index.js';
describe('decodeAddress', (): void => {
it('decodes an address', (): void => {
expect(
decodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')
).toEqual(
ALICE_PUBLIC_SR
);
});
it('decodes the council address', (): void => {
expect(
u8aToHex(decodeAddress('F3opxRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29'))
).toEqual(u8aToHex(stringToU8a('modlpy/trsry'.padEnd(32, '\0'))));
});
it('converts a publicKey (u8a) as-is', (): void => {
expect(
decodeAddress(new Uint8Array([1, 2, 3]))
).toEqual(
new Uint8Array([1, 2, 3])
);
});
it('converts a publicKey (hex) as-is', (): void => {
expect(
decodeAddress('0x01020304')
).toEqual(
new Uint8Array([1, 2, 3, 4])
);
});
it('decodes a short address', (): void => {
expect(
decodeAddress('F7NZ')
).toEqual(new Uint8Array([1]));
});
it('decodes a 1-byte accountId (with prefix)', (): void => {
expect(
decodeAddress('g4b', false, 2)
).toEqual(new Uint8Array([1]));
});
it('decodes a 2-byte accountId', (): void => {
expect(
decodeAddress('3xygo', false, 2)
).toEqual(new Uint8Array([0, 1]));
});
it('encodes a 4-byte address', (): void => {
expect(
decodeAddress('zswfoZa', false, 2)
).toEqual(new Uint8Array([1, 2, 3, 4]));
});
it('decodes a 8-byte address', (): void => {
expect(
decodeAddress('848Gh2GcGaZia', false, 2)
).toEqual(new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]));
});
it('decodes a 33-byte address', (): void => {
expect(
decodeAddress('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou')
).toEqual(
hexToU8a('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
);
});
it('decodes a 2-byte prefix (65)', (): void => {
expect(
decodeAddress('cLtA6nCDyvwKcEHH4QkZDSHMhS9s78BvUJUsKUbUAn1Jc2SCF')
).toEqual(
hexToU8a('0x08e8969768fc14399930d4b8d693f68a2ff6c6a597325d6946095e5e9d9d1b0e')
);
});
it('decodes a 2-byte prefix (69)', (): void => {
expect(
decodeAddress('cnUaoo5wodnTVA4bnr4woSweto8hWZADUvLFXkR9Q6U7BRsbF')
).toEqual(
hexToU8a('0x88eafe0305d460d1695cf34c2f786050df8e40d215e488790cc70929c9e8316d')
);
});
it('decodes a 2-byte prefix (252)', (): void => {
expect(
decodeAddress('xw9Hca4RJTmBRgzJT4ieJBh7XCK9gE3NXBDSEmgGHd4TCrbnG')
).toEqual(
hexToU8a('0xfc422da6c3bc6dfa2a436a506428072941662f816987baaa8914e02ff5947f4b')
);
});
it('decodes a 2-byte prefix (255)', (): void => {
expect(
decodeAddress('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k')
).toEqual(
decodeAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua')
);
});
it('decodes a 2-byte prefix (ecdsa, from Substrate)', (): void => {
expect(
u8aToHex(decodeAddress('4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV'))
).toEqual('0x035676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba9');
});
it('fails when length is invalid', (): void => {
expect(
(): Uint8Array => decodeAddress('y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b')
).toThrow(/address length/);
});
it('fails when the checksum does not match', (): void => {
expect(
(): Uint8Array => decodeAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMa9cj')
).toThrow(/address checksum/);
expect(
(): Uint8Array => decodeAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU')
).toThrow(/address checksum/);
});
it('fails when invalid base58 encoded address is found', (): void => {
expect(
() => u8aToHex(decodeAddress('F3opIRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29'))
).toThrow(/Decoding F3opIRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29: Invalid base58 character "I" \(0x49\) at index 4/);
});
});
@@ -0,0 +1,41 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
// Original implementation: https://github.com/paritytech/polka-ui/blob/4858c094684769080f5811f32b081dd7780b0880/src/polkadot.js#L6
import { isHex, isU8a, u8aToU8a } from '@pezkuwi/util';
import { base58Decode } from '../base58/index.js';
import { checkAddressChecksum } from './checksum.js';
import { defaults } from './defaults.js';
export function decodeAddress (encoded?: string | Uint8Array | null, ignoreChecksum?: boolean, ss58Format: Prefix = -1): Uint8Array {
if (!encoded) {
throw new Error('Invalid empty address passed');
}
if (isU8a(encoded) || isHex(encoded)) {
return u8aToU8a(encoded);
}
try {
const decoded = base58Decode(encoded);
if (!defaults.allowedEncodedLengths.includes(decoded.length)) {
throw new Error('Invalid decoded address length');
}
const [isValid, endPos, ss58Length, ss58Decoded] = checkAddressChecksum(decoded);
if (!isValid && !ignoreChecksum) {
throw new Error('Invalid decoded address checksum');
} else if (ss58Format !== -1 && ss58Format !== ss58Decoded) {
throw new Error(`Expected ss58Format ${ss58Format}, received ${ss58Decoded}`);
}
return decoded.slice(ss58Length, endPos);
} catch (error) {
throw new Error(`Decoding ${encoded}: ${(error as Error).message}`);
}
}
@@ -0,0 +1,12 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { availableNetworks } from '../networks.js';
export const defaults = {
allowedDecodedLengths: [1, 2, 4, 8, 32, 33],
// publicKey has prefix + 2 checksum bytes, short only prefix + 1 checksum byte
allowedEncodedLengths: [3, 4, 6, 10, 35, 36, 37, 38],
allowedPrefix: availableNetworks.map(({ prefix }) => prefix),
prefix: 42
};
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { waitReady } from '@pezkuwi/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/);
});
});
@@ -0,0 +1,36 @@
// Copyright 2017-2025 @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);
}
@@ -0,0 +1,177 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { encodeAddress } from './index.js';
// eslint-disable-next-line jest/no-export
export const ALICE_PUBLIC_SR = new Uint8Array([212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125]);
// eslint-disable-next-line jest/no-export
export const ALICE_PUBLIC_ED = 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]);
const SUBKEY = [
{
// substrate default
address: '5DA4D4GL5iakrn22h5uKoevgvo18Pqj5BcdEUv8etEDPdijA',
publicKey: '0x3050f8456519829fe03302da802d22d3233a5f4037b9a3e2bcc403ccfcb2d735',
ss58Format: 42
},
{
// aventus
address: 'cLtA6nCDyvwKcEHH4QkZDSHMhS9s78BvUJUsKUbUAn1Jc2SCF',
publicKey: '0x08e8969768fc14399930d4b8d693f68a2ff6c6a597325d6946095e5e9d9d1b0e',
ss58Format: 65
},
{
// crust
address: 'cTGShekJ1L1UKFZR9xmv9UTJod7vqjFAPo4sDhXih2c3y1yLS',
publicKey: '0x04a047d52fe542484c69bc528990cfeaf3a663dded0638ee1b51cf78bacd1072',
ss58Format: 66
},
{
// sora
address: 'cnVRwXfAnz3RSVQyBUC8f8McrK3YBX2QYd4WoctpeSC6VTJYm',
publicKey: '0xae640d53cfa815f4a6a50ae70235cd7d9d134d0f1c3a4ccd118e321dfb6ab51f',
ss58Format: 69
},
{
// ecdsa
address: '4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV',
publicKey: '0x035676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba9',
ss58Format: 200
},
{
// social-network
address: 'xw5g1Eec8LT99pZLZMaTWwrwvNtfM6vrSuZeVbtEszCDUwByg',
publicKey: '0x5c64f1151f0ce4358c27238fb20c88e7c899825436f565410724c8c2c5add869',
ss58Format: 252
},
{
address: 'yGF4JP7q5AK46d1FPCEm9sYQ4KooSjHMpyVAjLnsCSWVafPnf',
publicKey: '0x66cd6cf085627d6c85af1aaf2bd10cf843033e929b4e3b1c2ba8e4aa46fe111b',
ss58Format: 255
},
{
address: 'yGDYxQatQwuxqT39Zs4LtcTnpzE12vXb7ZJ6xpdiHv6gTu1hF',
publicKey: '0x242fd5a078ac6b7c3c2531e9bcf1314343782aeb58e7bc6880794589e701db55',
ss58Format: 255
},
{
address: 'mHm8k9Emsvyfp3piCauSH684iA6NakctF8dySQcX94GDdrJrE',
publicKey: '0x44d5a3ac156335ea99d33a83c57c7146c40c8e2260a8a4adf4e7a86256454651',
ss58Format: 4242
},
{
address: 'r6Gr4gaMP8TsjhFbqvZhv3YvnasugLiRJpzpRHifsqqG18UXa',
publicKey: '0x88f01441682a17b52d6ae12d1a5670cf675fd254897efabaa5069eb3a701ab73',
ss58Format: 14269
}
];
describe('encode', (): void => {
it('encodes an address to a valid value', (): void => {
expect(
encodeAddress(ALICE_PUBLIC_ED)
).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');
});
it('can re-encode an address', (): void => {
expect(
encodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 2)
).toEqual('HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F');
});
it('can re-encode an address to Polkadot live', (): void => {
expect(
encodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 0)
).toEqual('15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5');
});
it('fails when non-valid publicKey provided', (): void => {
expect(
(): string => encodeAddress(
ALICE_PUBLIC_ED.slice(0, 30)
)
).toThrow(/Expected a valid key/);
});
it('encodes a 1-byte address', (): void => {
expect(
encodeAddress(
new Uint8Array([1])
)
).toEqual('F7NZ');
});
it('encodes a 1-byte address (with prefix)', (): void => {
expect(
encodeAddress(
new Uint8Array([1]), 2
)
).toEqual('g4b');
});
it('encodes a 2-byte address', (): void => {
expect(
encodeAddress(
new Uint8Array([0, 1]), 2
)
).toEqual('3xygo');
});
it('encodes a 4-byte address', (): void => {
expect(
encodeAddress(
new Uint8Array([1, 2, 3, 4]), 2
)
).toEqual('zswfoZa');
});
it('encodes a 8-byte address', (): void => {
expect(
encodeAddress(
new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]), 2
)
).toEqual('848Gh2GcGaZia');
});
it('encodes an 33-byte address', (): void => {
expect(
encodeAddress('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
).toEqual('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou');
});
it('encodes with 2 byte prefix', (): void => {
expect(
encodeAddress(ALICE_PUBLIC_ED, 255)
).toEqual('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k');
});
SUBKEY.forEach(({ address, publicKey, ss58Format }, index): void => {
it(`encodes with Subkey equality (${index} - ${ss58Format})`, (): void => {
expect(
encodeAddress(publicKey, ss58Format)
).toEqual(address);
});
});
it('does not encode for > 16,383, < 0', (): void => {
expect(
() => encodeAddress(ALICE_PUBLIC_ED, -1)
).toThrow(/range ss58Format specified/);
expect(
() => encodeAddress(ALICE_PUBLIC_ED, 16384)
).toThrow(/range ss58Format specified/);
});
it('does not encode reserved', (): void => {
expect(
() => encodeAddress(ALICE_PUBLIC_ED, 46)
).toThrow(/range ss58Format specified/);
expect(
() => encodeAddress(ALICE_PUBLIC_ED, 47)
).toThrow(/range ss58Format specified/);
});
});
@@ -0,0 +1,43 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
// Original implementation: https://github.com/paritytech/polka-ui/blob/4858c094684769080f5811f32b081dd7780b0880/src/polkadot.js#L34
import { u8aConcat } from '@pezkuwi/util';
import { base58Encode } from '../base58/index.js';
import { decodeAddress } from './decode.js';
import { defaults } from './defaults.js';
import { sshash } from './sshash.js';
export function encodeAddress (key: string | Uint8Array, ss58Format: Prefix = defaults.prefix): string {
// decode it, this means we can re-encode an address
const u8a = decodeAddress(key);
if ((ss58Format < 0) || (ss58Format > 16383 && !ss58Exceptions.includes(ss58Format)) || [46, 47].includes(ss58Format)) {
throw new Error('Out of range ss58Format specified');
} else if (!defaults.allowedDecodedLengths.includes(u8a.length)) {
throw new Error(`Expected a valid key to convert, with length ${defaults.allowedDecodedLengths.join(', ')}`);
}
const input = u8aConcat(
ss58Format < 64
? [ss58Format]
: [
((ss58Format & 0b0000_0000_1111_1100) >> 2) | 0b0100_0000,
(ss58Format >> 8) | ((ss58Format & 0b0000_0000_0000_0011) << 6)
],
u8a
);
return base58Encode(
u8aConcat(
input,
sshash(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1)
)
);
}
// Exceptions like 29972 (Mythos chain) which uses Ethereum style account (not ss58 Encoded)
const ss58Exceptions = [29972];
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { encodeDerivedAddress } from './index.js';
describe('encodeDerivedAddress', (): void => {
it('creates a valid known derived address', (): void => {
expect(
encodeDerivedAddress('5GvUh7fGKsdBEh5XpypkfkGuf7j3vXLxH9BdxjxnJNVXRYi1', 0)
).toEqual('5E5XxqPxm7QbEs6twYfp3tyjXidn4kqRrNPH4o6JK9JSLUeD');
});
});
@@ -0,0 +1,19 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@pezkuwi/util';
import type { Prefix } from './types.js';
import { decodeAddress } from './decode.js';
import { encodeAddress } from './encode.js';
import { createKeyDerived } from './keyDerived.js';
/**
* @name encodeDerivedAddress
* @summary Creates a derived address as used in Substrate utility.
* @description
* Creates a Substrate derived address based on the input address/publicKey and the index supplied.
*/
export function encodeDerivedAddress (who: string | Uint8Array, index: bigint | BN | number, ss58Format?: Prefix): string {
return encodeAddress(createKeyDerived(decodeAddress(who), index), ss58Format);
}
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { encodeMultiAddress } from './index.js';
describe('encodeMultiAddress', (): void => {
it('creates a valid known multi address', (): void => {
expect(
encodeMultiAddress([
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
'5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y'
], 2)
).toEqual('5DjYJStmdZ2rcqXbXGX7TW85JsrW6uG4y9MUcLq2BoPMpRA7');
});
});
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@pezkuwi/util';
import type { Prefix } from './types.js';
import { encodeAddress } from './encode.js';
import { createKeyMulti } from './keyMulti.js';
/**
* @name encodeMultiAddress
* @summary Creates a multisig address.
* @description
* Creates a Substrate multisig address based on the input address and the required threshold.
*/
export function encodeMultiAddress (who: (string | Uint8Array)[], threshold: bigint | BN | number, ss58Format?: Prefix): string {
return encodeAddress(createKeyMulti(who, threshold), ss58Format);
}
@@ -0,0 +1,45 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { ALICE_PUBLIC_SR } from './encode.spec.js';
import { addressEq } from './index.js';
describe('addressEq', (): void => {
it('returns false with non-equal', (): void => {
expect(
addressEq(
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'5EnxxUmEbw8DkENKiYuZ1DwQuMoB2UWEQJZZXrTsxoz7SpgG'
)
).toEqual(false);
});
it('returns true for equal, matching prefix', (): void => {
expect(
addressEq(
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
)
).toEqual(true);
});
it('returns true for equal, non-matching prefix', (): void => {
expect(
addressEq(
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5'
)
).toEqual(true);
});
it('returns true for equal, address vs publicKey', (): void => {
expect(
addressEq(
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
ALICE_PUBLIC_SR
)
).toEqual(true);
});
});
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { u8aEq } from '@pezkuwi/util';
import { decodeAddress } from './decode.js';
/**
* @name addressEq
* @summary Compares two addresses, either in ss58, Uint8Array or hex format.
* @description
* For the input values, return true is the underlying public keys do match.
* @example
* <BR>
*
* ```javascript
* import { u8aEq } from '@pezkuwi/util';
*
* u8aEq(new Uint8Array([0x68, 0x65]), new Uint8Array([0x68, 0x65])); // true
* ```
*/
export function addressEq (a: string | Uint8Array, b: string | Uint8Array): boolean {
return u8aEq(decodeAddress(a), decodeAddress(b));
}
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { evmToAddress } from './index.js';
describe('evmToAddress', (): void => {
it('creates a valid known SS58 address', (): void => {
expect(
evmToAddress('0xd43593c715fdd31c61141abd04a99fd6822c8558', 42, 'blake2')
).toEqual('5FrLxJsyJ5x9n2rmxFwosFraxFCKcXZDngRLNectCn64UjtZ');
});
it('fails when length is invalid', (): void => {
expect(
() => evmToAddress('0x1234567890ABCDEF1234567890ABCDEF')
).toThrow(/address length/);
});
});
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HashType } from '../secp256k1/types.js';
import type { Prefix } from './types.js';
import { u8aConcat } from '@pezkuwi/util';
import { hasher } from '../secp256k1/hasher.js';
import { encodeAddress } from './encode.js';
/**
* @name evmToAddress
* @summary Converts an EVM address to its corresponding SS58 address.
*/
export function evmToAddress (evmAddress: string | Uint8Array, ss58Format?: Prefix, hashType: HashType = 'blake2'): string {
const message = u8aConcat('evm:', evmAddress);
if (message.length !== 24) {
throw new Error(`Converting ${evmAddress as string}: Invalid evm address length`);
}
return encodeAddress(hasher(hashType, message), ss58Format);
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
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';
export { addressEq } from './eq.js';
export { evmToAddress } from './evmToAddress.js';
export { isAddress } from './is.js';
export { createKeyDerived } from './keyDerived.js';
export { createKeyMulti } from './keyMulti.js';
export { sortAddresses } from './sort.js';
export { validateAddress } from './validate.js';
// eslint-disable-next-line deprecation/deprecation
export { setSS58Format } from './setSS58Format.js';
+113
View File
@@ -0,0 +1,113 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { isAddress } from './index.js';
describe('isAddress', (): void => {
it('decodes an address', (): void => {
expect(
isAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')
).toEqual(true);
});
it('decodes the council address', (): void => {
expect(
isAddress('F3opxRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29')
).toEqual(true);
});
it('converts a publicKey (hex) as-is', (): void => {
expect(
isAddress('0x01020304')
).toEqual(true);
});
it('decodes a short address', (): void => {
expect(
isAddress('F7NZ')
).toEqual(true);
});
it('decodes a 1-byte accountId (with prefix)', (): void => {
expect(
isAddress('g4b', false, 2)
).toEqual(true);
});
it('decodes a 2-byte accountId', (): void => {
expect(
isAddress('3xygo', false, 2)
).toEqual(true);
});
it('encodes a 4-byte address', (): void => {
expect(
isAddress('zswfoZa', false, 2)
).toEqual(true);
});
it('decodes a 8-byte address', (): void => {
expect(
isAddress('848Gh2GcGaZia', false, 2)
).toEqual(true);
});
it('decodes a 33-byte address', (): void => {
expect(
isAddress('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou')
).toEqual(true);
});
it('decodes a 2-byte prefix (65)', (): void => {
expect(
isAddress('cLtA6nCDyvwKcEHH4QkZDSHMhS9s78BvUJUsKUbUAn1Jc2SCF')
).toEqual(true);
});
it('decodes a 2-byte prefix (69)', (): void => {
expect(
isAddress('cnUaoo5wodnTVA4bnr4woSweto8hWZADUvLFXkR9Q6U7BRsbF')
).toEqual(true);
});
it('decodes a 2-byte prefix (252)', (): void => {
expect(
isAddress('xw9Hca4RJTmBRgzJT4ieJBh7XCK9gE3NXBDSEmgGHd4TCrbnG')
).toEqual(true);
});
it('decodes a 2-byte prefix (255)', (): void => {
expect(
isAddress('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k')
).toEqual(true);
});
it('decodes a 2-byte prefix (ecdsa, from Substrate)', (): void => {
expect(
isAddress('4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV')
).toEqual(true);
});
it('fails when length is invalid', (): void => {
expect(
isAddress('y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b')
).toEqual(false);
});
it('fails when the checksum does not match', (): void => {
expect(
isAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMa9cj')
).toEqual(false);
expect(
isAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU')
).toEqual(false);
});
it('fails when invalid base58 encoded address is found', (): void => {
expect(
isAddress('F3opIRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29')
).toEqual(false);
});
});
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
import { validateAddress } from './validate.js';
export function isAddress (address?: string | null, ignoreChecksum?: boolean, ss58Format?: Prefix): address is string {
try {
return validateAddress(address, ignoreChecksum, ss58Format);
} catch {
return false;
}
}
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { createKeyDerived } from './index.js';
describe('createKeyDerived', (): void => {
it('matches sub accounts with Rust', (): void => {
expect(
createKeyDerived(new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0]), 0)
).toEqual(
new Uint8Array([234, 236, 28, 96, 177, 168, 152, 193, 71, 179, 226, 102, 179, 155, 188, 240, 90, 182, 21, 175, 47, 47, 250, 179, 178, 0, 81, 222, 70, 56, 52, 234])
);
});
it('creates a valid subkey', (): void => {
expect(
createKeyDerived('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 1)
).toEqual(
new Uint8Array([248, 19, 86, 209, 254, 89, 84, 48, 54, 128, 166, 239, 153, 212, 143, 34, 191, 60, 210, 50, 39, 77, 122, 71, 29, 60, 247, 198, 95, 101, 246, 83])
);
});
});
@@ -0,0 +1,22 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@pezkuwi/util';
import { bnToU8a, stringToU8a, u8aConcat } from '@pezkuwi/util';
import { blake2AsU8a } from '../blake2/asU8a.js';
import { BN_LE_16_OPTS } from '../bn.js';
import { decodeAddress } from './decode.js';
const PREFIX = stringToU8a('modlpy/utilisuba');
export function createKeyDerived (who: string | Uint8Array, index: bigint | BN | number): Uint8Array {
return blake2AsU8a(
u8aConcat(
PREFIX,
decodeAddress(who),
bnToU8a(index, BN_LE_16_OPTS)
)
);
}
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { createKeyMulti } from './index.js';
describe('createKeyMulti', (): void => {
it('creates a valid multikey (aligning with Rust, needs sorting)', (): void => {
expect(
createKeyMulti([
new Uint8Array([1, 0, 0, 0, 0, 0, 0, 0]),
new Uint8Array([3, 0, 0, 0, 0, 0, 0, 0]),
new Uint8Array([2, 0, 0, 0, 0, 0, 0, 0])
], 2)
).toEqual(
new Uint8Array([67, 151, 196, 155, 179, 207, 47, 123, 90, 2, 35, 54, 162, 111, 241, 226, 88, 148, 54, 193, 252, 195, 93, 101, 16, 5, 93, 101, 186, 186, 254, 79])
);
});
});
@@ -0,0 +1,23 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { BN } from '@pezkuwi/util';
import { bnToU8a, compactToU8a, stringToU8a, u8aConcat, u8aSorted } from '@pezkuwi/util';
import { blake2AsU8a } from '../blake2/asU8a.js';
import { BN_LE_16_OPTS } from '../bn.js';
import { addressToU8a } from './util.js';
const PREFIX = stringToU8a('modlpy/utilisuba');
export function createKeyMulti (who: (string | Uint8Array)[], threshold: bigint | BN | number): Uint8Array {
return blake2AsU8a(
u8aConcat(
PREFIX,
compactToU8a(who.length),
...u8aSorted(who.map(addressToU8a)),
bnToU8a(threshold, BN_LE_16_OPTS)
)
);
}
@@ -0,0 +1,21 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { encodeAddress, setSS58Format } from './index.js';
describe('setSS58Format', (): void => {
beforeEach((): void => {
// eslint-disable-next-line deprecation/deprecation
setSS58Format(2);
});
it('sets and allows encoding using', (): void => {
expect(
encodeAddress(
new Uint8Array([1])
)
).toEqual('g4b');
});
});
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
import { logger } from '@pezkuwi/util';
import { defaults } from './defaults.js';
const l = logger('setSS58Format');
/**
* @description Sets the global SS58 format to use for address encoding
* @deprecated Use keyring.setSS58Format
*/
export function setSS58Format (prefix: Prefix): void {
l.warn('Global setting of the ss58Format is deprecated and not recommended. Set format on the keyring (if used) or as part of the address encode function');
defaults.prefix = prefix;
}
@@ -0,0 +1,22 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { sortAddresses } from './index.js';
describe('sortAddresses', (): void => {
it('sorts addresses by the publicKeys', (): void => {
expect(
sortAddresses([
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
'5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y'
])
).toEqual([
'5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty',
'5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y',
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY'
]);
});
});
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
import { u8aSorted } from '@pezkuwi/util';
import { encodeAddress } from './encode.js';
import { addressToU8a } from './util.js';
export function sortAddresses (addresses: (string | Uint8Array)[], ss58Format?: Prefix): string[] {
const u8aToAddress = (u8a: Uint8Array) => encodeAddress(u8a, ss58Format);
return u8aSorted(
addresses.map(addressToU8a)
).map(u8aToAddress);
}
@@ -0,0 +1,12 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { stringToU8a, u8aConcat } from '@pezkuwi/util';
import { blake2AsU8a } from '../blake2/asU8a.js';
const SS58_PREFIX = stringToU8a('SS58PRE');
export function sshash (key: Uint8Array): Uint8Array {
return blake2AsU8a(u8aConcat(SS58_PREFIX, key), 512);
}
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
// FIXME we really want this to map with what is in the allowedSS58 array... i.e. the
// values there. As of now, we just map to number.
export type Prefix = number;
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { decodeAddress } from './decode.js';
export function addressToU8a (who: string | Uint8Array): Uint8Array {
return decodeAddress(who);
}
@@ -0,0 +1,113 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { validateAddress } from './index.js';
describe('validateAddress', (): void => {
it('decodes an address', (): void => {
expect(
validateAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY')
).toEqual(true);
});
it('decodes the council address', (): void => {
expect(
validateAddress('F3opxRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29')
).toEqual(true);
});
it('converts a publicKey (hex) as-is', (): void => {
expect(
validateAddress('0x01020304')
).toEqual(true);
});
it('decodes a short address', (): void => {
expect(
validateAddress('F7NZ')
).toEqual(true);
});
it('decodes a 1-byte accountId (with prefix)', (): void => {
expect(
validateAddress('g4b', false, 2)
).toEqual(true);
});
it('decodes a 2-byte accountId', (): void => {
expect(
validateAddress('3xygo', false, 2)
).toEqual(true);
});
it('encodes a 4-byte address', (): void => {
expect(
validateAddress('zswfoZa', false, 2)
).toEqual(true);
});
it('decodes a 8-byte address', (): void => {
expect(
validateAddress('848Gh2GcGaZia', false, 2)
).toEqual(true);
});
it('decodes a 33-byte address', (): void => {
expect(
validateAddress('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou')
).toEqual(true);
});
it('decodes a 2-byte prefix (65)', (): void => {
expect(
validateAddress('cLtA6nCDyvwKcEHH4QkZDSHMhS9s78BvUJUsKUbUAn1Jc2SCF')
).toEqual(true);
});
it('decodes a 2-byte prefix (69)', (): void => {
expect(
validateAddress('cnUaoo5wodnTVA4bnr4woSweto8hWZADUvLFXkR9Q6U7BRsbF')
).toEqual(true);
});
it('decodes a 2-byte prefix (252)', (): void => {
expect(
validateAddress('xw9Hca4RJTmBRgzJT4ieJBh7XCK9gE3NXBDSEmgGHd4TCrbnG')
).toEqual(true);
});
it('decodes a 2-byte prefix (255)', (): void => {
expect(
validateAddress('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k')
).toEqual(true);
});
it('decodes a 2-byte prefix (ecdsa, from Substrate)', (): void => {
expect(
validateAddress('4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV')
).toEqual(true);
});
it('fails when length is invalid', (): void => {
expect(
() => validateAddress('y9EMHt34JJo4rWLSaxoLGdYXvjgSXEd4zHUnQgfNzwES8b')
).toThrow(/address length/);
});
it('fails when the checksum does not match', (): void => {
expect(
() => validateAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMa9cj')
).toThrow(/address checksum/);
expect(
() => validateAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDwU')
).toThrow(/address checksum/);
});
it('fails when invalid base58 encoded address is found', (): void => {
expect(
() => validateAddress('F3opIRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29')
).toThrow(/Decoding F3opIRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29: Invalid base58 character "I" \(0x49\) at index 4/);
});
});
@@ -0,0 +1,10 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Prefix } from './types.js';
import { decodeAddress } from './decode.js';
export function validateAddress (encoded?: string | null, ignoreChecksum?: boolean, ss58Format?: Prefix): encoded is string {
return !!decodeAddress(encoded, ignoreChecksum, ss58Format);
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { utils } from '@scure/base';
import { createDecode, createEncode, createIs, createValidate } from './helpers.js';
const chars = 'abcdefghijklmnopqrstuvwxyz234567';
const config = {
chars,
coder: utils.chain(
// We define our own chain, the default base32 has padding
utils.radix2(5),
utils.alphabet(chars),
{
decode: (input: string) => input.split(''),
encode: (input: string[]) => input.join('')
}
),
ipfs: 'b',
type: 'base32'
};
/**
* @name base32Validate
* @summary Validates a base32 value.
* @description
* Validates that the supplied value is valid base32, throwing exceptions if not
*/
export const base32Validate = /*#__PURE__*/ createValidate(config);
/**
* @name isBase32
* @description Checks if the input is in base32, returning true/false
*/
export const isBase32 = /*#__PURE__*/ createIs(base32Validate);
/**
* @name base32Decode
* @summary Delookup a base32 value.
* @description
* From the provided input, decode the base32 and return the result as an `Uint8Array`.
*/
export const base32Decode = /*#__PURE__*/ createDecode(config, base32Validate);
/**
* @name base32Encode
* @summary Creates a base32 value.
* @description
* From the provided input, create the base32 and return the result as a string.
*/
export const base32Encode = /*#__PURE__*/ createEncode(config);
@@ -0,0 +1,34 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { u8aToString } from '@pezkuwi/util';
import { base32Decode } from './index.js';
describe('base32Decode', (): void => {
it('decodes an empty string)', (): void => {
expect(
u8aToString(
base32Decode('')
)
).toEqual('');
});
it('decodes a base32', (): void => {
expect(
u8aToString(
base32Decode('irswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb')
)
).toEqual('Decentralize everything!!');
});
it('decodes a base32 (ipfsCompat)', (): void => {
expect(
u8aToString(
base32Decode('birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb', true)
)
).toEqual('Decentralize everything!!');
});
});
@@ -0,0 +1,30 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base58Decode } from '../base58/index.js';
import { base32Encode } from './index.js';
describe('base32Encode', (): void => {
it('encodes to a base32', (): void => {
expect(
base32Encode('Decentralize everything!!')
).toEqual('irswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb');
});
it('encodes to a base32 (ipfs-compat)', (): void => {
expect(
base32Encode('Decentralize everything!!', true)
).toEqual('birswgzloorzgc3djpjssazlwmvzhs5dinfxgoijb');
});
it('encodes a base58 to a base32', (): void => {
expect(
base32Encode(
base58Decode('zb2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV', true),
true
)
).toEqual('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy');
});
});
@@ -0,0 +1,93 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { U8aLike } from '@pezkuwi/util/types';
import { u8aToU8a } from '@pezkuwi/util';
// re-export the type so *.d.ts files don't have ../src imports
export type { U8aLike } from '@pezkuwi/util/types';
interface Coder {
decode: (value: string) => Uint8Array;
encode: (value: Uint8Array) => string;
}
interface Config {
chars: string;
coder: Coder;
ipfs?: string;
regex?: RegExp;
type: string;
withPadding?: boolean;
}
type DecodeFn = (value: string, ipfsCompat?: boolean) => Uint8Array;
type EncodeFn = (value: U8aLike, ipfsCompat?: boolean) => string;
type ValidateFn = (value?: unknown, ipfsCompat?: boolean) => value is string;
/** @internal */
export function createDecode ({ coder, ipfs }: Config, validate: ValidateFn): DecodeFn {
return (value: string, ipfsCompat?: boolean): Uint8Array => {
validate(value, ipfsCompat);
return coder.decode(
ipfs && ipfsCompat
? value.substring(1)
: value
);
};
}
/** @internal */
export function createEncode ({ coder, ipfs }: Config): EncodeFn {
return (value: U8aLike, ipfsCompat?: boolean): string => {
const out = coder.encode(u8aToU8a(value));
return ipfs && ipfsCompat
? `${ipfs}${out}`
: out;
};
}
/** @internal */
export function createIs (validate: ValidateFn): ValidateFn {
return (value?: unknown, ipfsCompat?: boolean): value is string => {
try {
return validate(value, ipfsCompat);
} catch {
return false;
}
};
}
/** @internal */
export function createValidate ({ chars, ipfs, type, withPadding }: Config): ValidateFn {
return (value?: unknown, ipfsCompat?: boolean): value is string => {
if (typeof value !== 'string') {
throw new Error(`Expected ${type} string input`);
} else if (ipfs && ipfsCompat && !value.startsWith(ipfs)) {
throw new Error(`Expected ipfs-compatible ${type} to start with '${ipfs}'`);
}
for (let i = (ipfsCompat ? 1 : 0), count = value.length; i < count; i++) {
if (chars.includes(value[i])) {
// all ok, character found
} else if (withPadding && value[i] === '=') {
if (i === count - 1) {
// last character, everything ok
} else if (value[i + 1] === '=') {
// next one is also padding, sequence ok
} else {
throw new Error(`Invalid ${type} padding sequence "${value[i]}${value[i + 1]}" at index ${i}`);
}
} else {
throw new Error(`Invalid ${type} character "${value[i]}" (0x${value.charCodeAt(i).toString(16)}) at index ${i}`);
}
}
return true;
};
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Encode and decode base32 values
*/
export { base32Decode, base32Encode, base32Validate, isBase32 } from './bs32.js';
@@ -0,0 +1,32 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { isBase32 } from './index.js';
describe('isBase32', (): void => {
it('validates encoded', (): void => {
expect(
isBase32('afkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', false)
).toEqual(true);
});
it('validates ipfs-compat encoded', (): void => {
expect(
isBase32('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', false)
).toEqual(true);
});
it('fails on invalid', (): void => {
expect(
isBase32('not in base32')
).toEqual(false);
});
it('fails on non-ipfs', (): void => {
expect(
isBase32('afkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true)
).toEqual(false);
});
});
@@ -0,0 +1,44 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base32Validate } from './index.js';
describe('base32Validate', (): void => {
it('validates encoded', (): void => {
expect(
base32Validate('afkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', false)
).toEqual(true);
});
it('validates ipfs-compat encoded', (): void => {
expect(
base32Validate('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true)
).toEqual(true);
});
it('does not fail on empty', (): void => {
expect(
base32Validate('')
).toEqual(true);
});
it('fails on non-string', (): void => {
expect(
() => base32Validate(true)
).toThrow(/Expected/);
});
it('fails on invalid', (): void => {
expect(
() => base32Validate('not in base32')
).toThrow(/Invalid/);
});
it('fails on non-ipfs', (): void => {
expect(
() => base32Validate('afkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true)
).toThrow(/Expected/);
});
});
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { base58 } from '@scure/base';
import { createDecode, createEncode, createIs, createValidate } from '../base32/helpers.js';
const config = {
chars: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz',
coder: base58,
ipfs: 'z',
type: 'base58'
};
/**
* @name base58Validate
* @summary Validates a base58 value.
* @description
* Validates that the supplied value is valid base58, throwing exceptions if not
*/
export const base58Validate = /*#__PURE__*/ createValidate(config);
/**
* @name base58Decode
* @summary Decodes a base58 value.
* @description
* From the provided input, decode the base58 and return the result as an `Uint8Array`.
*/
export const base58Decode = /*#__PURE__*/ createDecode(config, base58Validate);
/**
* @name base58Encode
* @summary Creates a base58 value.
* @description
* From the provided input, create the base58 and return the result as a string.
*/
export const base58Encode = /*#__PURE__*/ createEncode(config);
/**
* @name isBase58
* @description Checks if the input is in base58, returning true/false
*/
export const isBase58 = /*#__PURE__*/ createIs(base58Validate);
@@ -0,0 +1,31 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base32Decode } from '../base32/index.js';
import { base58Decode } from './index.js';
describe('base58Encode', (): void => {
it('decodes an empty string)', (): void => {
expect(
base58Decode('')
).toEqual(new Uint8Array());
});
it('encodes a base58 to a base32', (): void => {
expect(
base58Decode('b2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV')
).toEqual(
base32Decode('afkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy')
);
});
it('encodes a base58 to a base32 (ipfs-compat)', (): void => {
expect(
base58Decode('zb2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV', true)
).toEqual(
base32Decode('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true)
);
});
});
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base32Decode } from '../base32/index.js';
import { base58Encode } from './index.js';
describe('base58Encode', (): void => {
it('encodes a base32 to a base58', (): void => {
expect(
base58Encode(
base32Decode('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true)
)
).toEqual('b2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV');
});
it('encodes a base32 to a base58 (ipfs-compat)', (): void => {
expect(
base58Encode(
base32Decode('bafkreigh2akiscaildcqabsyg3dfr6chu3fgpregiymsck7e7aqa4s52zy', true),
true
)
).toEqual('zb2rhk6GMPQF3hfzwXTaNYFLKomMeC6UXdUt6jZKPpeVirLtV');
});
});
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Encode and decode base58 values
*/
export { base58Decode, base58Encode, base58Validate, isBase58 } from './bs58.js';
@@ -0,0 +1,20 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base58Validate } from './index.js';
describe('base58Validate', (): void => {
it('validates encoded', (): void => {
expect(
base58Validate('a1UbyspTdnyZXLUQaQbciCxrCWWxz24kgSwGXSQnkbs', false)
).toEqual(true);
});
it('fails on string with extra padding', (): void => {
expect(
() => base58Validate('a1UbyspTdnyZXLUQaQbciCxrCWWxz24kgSwGXSQnkbs=', false)
).toThrow(/Invalid base58 character "="/);
});
});
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { base64 } from '@scure/base';
import { createDecode, createEncode, createIs, createValidate } from '../base32/helpers.js';
const config = {
chars: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
coder: base64,
type: 'base64',
withPadding: true
};
/**
* @name base64Validate
* @summary Validates a base64 value.
* @description
* Validates that the supplied value is valid base64
*/
export const base64Validate = /*#__PURE__*/ createValidate(config);
/**
* @name isBase64
* @description Checks if the input is in base64, returning true/false
*/
export const isBase64 = /*#__PURE__*/ createIs(base64Validate);
/**
* @name base64Decode
* @summary Decodes a base64 value.
* @description
* From the provided input, decode the base64 and return the result as an `Uint8Array`.
*/
export const base64Decode = /*#__PURE__*/ createDecode(config, base64Validate);
/**
* @name base64Encode
* @summary Creates a base64 value.
* @description
* From the provided input, create the base64 and return the result as a string.
*/
export const base64Encode = /*#__PURE__*/ createEncode(config);
@@ -0,0 +1,42 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { stringToU8a } from '@pezkuwi/util';
import { base64Decode } from './index.js';
describe('base64Decode', (): void => {
it('decodes an empty string)', (): void => {
expect(
base64Decode('')
).toEqual(
stringToU8a('')
);
});
it('decodes a mixed base64 utf8 string (1)', (): void => {
expect(
base64Decode('aGVsbG8gd29ybGQg0J/RgNC40LLQtdGC0YHRgtCy0YPRjiDQvNC4IOS9oOWlvQ==')
).toEqual(
stringToU8a('hello world Приветствую ми 你好')
);
});
it('decodes a mixed base64 utf8 string (2)', (): void => {
expect(
base64Decode('4pyTIMOgIGxhIG1vZGU=')
).toEqual(
stringToU8a('✓ à la mode')
);
});
it('decodes a mixed base64 utf8 string (3)', (): void => {
expect(
base64Decode('SGVsbG8gV29ybGQh')
).toEqual(
stringToU8a('Hello World!')
);
});
});
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base64Encode } from './index.js';
describe('base64Encode', (): void => {
it('encodes a mixed base64 utf8 string', (): void => {
expect(
base64Encode('hello world Приветствую ми 你好')
).toEqual('aGVsbG8gd29ybGQg0J/RgNC40LLQtdGC0YHRgtCy0YPRjiDQvNC4IOS9oOWlvQ==');
});
});
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Encode and decode base64 values
*/
export { base64Decode, base64Encode, base64Validate, isBase64 } from './bs64.js';
export { base64Pad } from './pad.js';
export { base64Trim } from './trim.js';
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base64Pad } from './index.js';
describe('base64Pad', (): void => {
it('pads a utf-8 string', (): void => {
expect(
base64Pad('YWJjZA')
).toEqual('YWJjZA==');
});
});
+10
View File
@@ -0,0 +1,10 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name base64Pad
* @description Adds padding characters for correct length
*/
export function base64Pad (value: string): string {
return value.padEnd(value.length + (value.length % 4), '=');
}
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base64Trim } from './index.js';
describe('base64Trim', (): void => {
it('trims a utf-8 string', (): void => {
expect(
base64Trim('aGVsbG8gd29ybGQg0J/RgNC40LLQtdGC0YHRgtCy0YPRjiDQvNC4IOS9oOWlvQ==')
).toEqual('aGVsbG8gd29ybGQg0J/RgNC40LLQtdGC0YHRgtCy0YPRjiDQvNC4IOS9oOWlvQ');
});
});
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @name base64Trim
* @description Trims padding characters
*/
export function base64Trim (value: string): string {
while (value.length && value.endsWith('=')) {
value = value.slice(0, -1);
}
return value;
}
@@ -0,0 +1,32 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { base64Validate } from './index.js';
describe('base64Validate', (): void => {
it('validates a mixed base64 utf8 string', (): void => {
expect(
() => base64Validate('aGVsbG8gd29ybGQg0J/RgNC40LLQtd^GC0YHRgtCy0YPRjiDQvNC4IOS9oOWlvQ==')
).toThrow(/Invalid base64 character "\^" \(0x5e\) at index 30/);
});
it('validates with one extra padding character', (): void => {
expect(
base64Validate('bGlnaHQgd28=')
).toEqual(true);
});
it('validates with two extra padding characters', (): void => {
expect(
base64Validate('bGlnaHQgdw==')
).toEqual(true);
});
it('validates misplaced padding characters', (): void => {
expect(
() => base64Validate('bGlnaHQgdw=g=')
).toThrow(/Invalid base64 padding sequence "=g"/);
});
});
@@ -0,0 +1,57 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexToU8a } from '@pezkuwi/util';
import { waitReady } from '@pezkuwi/wasm-crypto';
import { blake2AsHex } from './index.js';
describe('blake2AsHex', (): void => {
beforeEach(async (): Promise<void> => {
await waitReady();
});
for (const onlyJs of [false, true]) {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
it('returns a 64-bit value (specified)', (): void => {
expect(
blake2AsHex('abc', 64, null, onlyJs)
).toEqual('0xd8bb14d833d59559');
});
it('returns a 128-bit value (as specified)', (): void => {
expect(
blake2AsHex('abc', 128, null, onlyJs)
).toEqual('0xcf4ab791c62b8d2b2109c90275287816');
});
it('returns a 128-bit value (as specified, with key)', (): void => {
expect(
blake2AsHex('abc', 128, new Uint8Array([1, 2]), onlyJs)
).toEqual('0x36f3d08cda72a00ddf2be103eb5770d9');
});
it('returns a 256-bit value (default)', (): void => {
expect(
blake2AsHex('abc', undefined, null, onlyJs)
).toEqual('0xbddd813c634239723171ef3fee98579b94964e3bb1cb3e427262c8c068d52319');
});
it('returns a 512-bit value (as specified)', (): void => {
expect(
blake2AsHex('abc', 512, null, onlyJs)
).toEqual('0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923');
});
it('matches with the Rust implementation', (): void => {
expect(
blake2AsHex(
hexToU8a('0x454545454545454545454545454545454545454545454545454545454545454501000000000000002481853da20b9f4322f34650fea5f240dcbfb266d02db94bfa0153c31f4a29dbdbf025dd4a69a6f4ee6e1577b251b655097e298b692cb34c18d3182cac3de0dc00000000'), 256, undefined, onlyJs
)
).toEqual('0x1025e5db74fdaf4d2818822dccf0e1604ae9ccc62f26cecfde23448ff0248abf');
});
});
}
});
@@ -0,0 +1,74 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hexToU8a } from '@pezkuwi/util';
import { waitReady } from '@pezkuwi/wasm-crypto';
import { perfWasm } from '../test/index.js';
import { blake2AsU8a } from './index.js';
describe('blake2AsU8a', (): void => {
beforeEach(async (): Promise<void> => {
await waitReady();
});
for (const onlyJs of [false, true]) {
describe(`onlyJs=${(onlyJs && 'true') || 'false'}`, (): void => {
it('returns a 64-bit value by default', (): void => {
expect(
blake2AsU8a('abc', undefined, undefined, onlyJs)
).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('returns a 128-bit value (as specified,)', (): void => {
expect(
blake2AsU8a('abc', 128, undefined, onlyJs)
).toEqual(
new Uint8Array([207, 74, 183, 145, 198, 43, 141, 43, 33, 9, 201, 2, 117, 40, 120, 22])
);
});
it('returns a 256-bit value (as specified)', (): void => {
expect(
blake2AsU8a('abc', 256, undefined, onlyJs)
).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('returns a 512-bit value (as specified)', (): void => {
expect(
blake2AsU8a('abc', 512, undefined, onlyJs)
).toEqual(
hexToU8a('0xba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923')
);
});
});
}
it('has equivalent Wasm/Js outputs on hex inputs', (): void => {
const a = blake2AsU8a('0x123456', 256, null, false);
const b = blake2AsU8a('0x123456', 256, null, true);
expect(a).toEqual(b);
});
it('has equivalent Wasm/Js outputs with key inputs', (): void => {
const a = blake2AsU8a('0x123456', 256, new Uint8Array([1, 2]), false);
const b = blake2AsU8a('0x123456', 256, new Uint8Array([1, 2]), true);
expect(a).toEqual(b);
});
for (const bitLength of [256, 512] as const) {
describe(`bitLength=${bitLength}`, (): void => {
perfWasm(`blake2AsU8a, bitLength=${bitLength}`, 64000, (input, onlyJs) =>
blake2AsU8a(input, bitLength, null, onlyJs)
);
});
}
});
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { blake2b as blake2bJs } from '@noble/hashes/blake2b';
import { hasBigInt, u8aToU8a } from '@pezkuwi/util';
import { blake2b, isReady } from '@pezkuwi/wasm-crypto';
import { createAsHex } from '../helpers.js';
/**
* @name blake2AsU8a
* @summary Creates a blake2b u8a from the input.
* @description
* From a `Uint8Array` input, create the blake2b and return the result as a u8a with the specified `bitLength`.
* @example
* <BR>
*
* ```javascript
* import { blake2AsU8a } from '@pezkuwi/util-crypto';
*
* blake2AsU8a('abc'); // => [0xba, 0x80, 0xa5, 0x3f, 0x98, 0x1c, 0x4d, 0x0d]
* ```
*/
export function blake2AsU8a (data: string | Uint8Array, bitLength: 64 | 128 | 256 | 384 | 512 = 256, key?: Uint8Array | null, onlyJs?: boolean): Uint8Array {
const byteLength = Math.ceil(bitLength / 8);
const u8a = u8aToU8a(data);
return !hasBigInt || (!onlyJs && isReady())
? blake2b(u8a, u8aToU8a(key), byteLength)
: key
? blake2bJs(u8a, { dkLen: byteLength, key })
: blake2bJs(u8a, { dkLen: byteLength });
}
/**
* @name blake2AsHex
* @description Creates a blake2b hex from the input.
*/
export const blake2AsHex = /*#__PURE__*/ createAsHex(blake2AsU8a);
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/**
* @summary Create blake2b values with specified bitlengths
*/
export { blake2AsHex, blake2AsU8a } from './asU8a.js';
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
export const BN_BE_OPTS = { isLe: false };
export const BN_LE_OPTS = { isLe: true };
export const BN_LE_16_OPTS = { bitLength: 16, isLe: true };
export const BN_BE_32_OPTS = { bitLength: 32, isLe: false };
export const BN_LE_32_OPTS = { bitLength: 32, isLe: true };
export const BN_BE_256_OPTS = { bitLength: 256, isLe: false };
export const BN_LE_256_OPTS = { bitLength: 256, isLe: true };
export const BN_LE_512_OPTS = { bitLength: 512, isLe: true };
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import './bundleInit.js';
// all named
export { packageInfo } from './packageInfo.js';
// all starred
export * from './address/index.js';
export * from './base32/index.js';
export * from './base58/index.js';
export * from './base64/index.js';
export * from './blake2/index.js';
export * from './crypto.js';
export * from './ed25519/index.js';
export * from './ethereum/index.js';
export * from './hd/index.js';
export * from './hmac/index.js';
export * from './json/index.js';
export * from './keccak/index.js';
export * from './key/index.js';
export * from './mnemonic/index.js';
export * from './nacl/index.js';
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';
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import '@polkadot/x-bigint/shim';
import { cryptoWaitReady } from './crypto.js';
// start init process immediately
cryptoWaitReady().catch((): void => {
// shouldn't happen, logged and caught inside cryptoWaitReady
});
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
describe('cryptoWaitReady', (): void => {
it('should return false when it cannot initialize', async (): Promise<void> => {
const old = global.WebAssembly;
global.WebAssembly = null as unknown as typeof WebAssembly;
const { cryptoWaitReady } = await import('./crypto.js');
expect(await cryptoWaitReady()).toBe(false);
global.WebAssembly = old;
});
});
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { isReady, waitReady } from '@pezkuwi/wasm-crypto';
export const cryptoIsReady = isReady;
export function cryptoWaitReady (): Promise<boolean> {
return waitReady()
.then((): boolean => {
if (!isReady()) {
throw new Error('Unable to initialize @polkadot/util-crypto');
}
return true;
})
.catch(() => false);
}
@@ -0,0 +1,18 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { compactAddLength, isU8a, stringToU8a, u8aConcat } from '@pezkuwi/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)
);
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright 2017-2025 @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';
@@ -0,0 +1,28 @@
// Copyright 2017-2025 @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);
});
});
@@ -0,0 +1,25 @@
// Copyright 2017-2025 @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 '@pezkuwi/util-crypto';
*
* ed25519PairFromRandom(); // => { secretKey: [...], publicKey: [...] }
* ```
*/
export function ed25519PairFromRandom (): Keypair {
return ed25519PairFromSeed(randomAsU8a());
}
@@ -0,0 +1,33 @@
// Copyright 2017-2025 @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
});
});
});
@@ -0,0 +1,29 @@
// Copyright 2017-2025 @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 '@pezkuwi/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
};
}
@@ -0,0 +1,42 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { stringToU8a } from '@pezkuwi/util';
import { waitReady } from '@pezkuwi/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);
});
});
});
@@ -0,0 +1,41 @@
// Copyright 2017-2025 @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 '@pezkuwi/util';
import { ed25519KeypairFromSeed, isReady } from '@pezkuwi/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 '@pezkuwi/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])
};
}
@@ -0,0 +1,17 @@
// Copyright 2017-2025 @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])
});
});
});
@@ -0,0 +1,31 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Keypair } from '../../types.js';
import { stringToU8a } from '@pezkuwi/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 '@pezkuwi/util-crypto';
*
* ed25519PairFromString('test'); // => { secretKey: [...], publicKey: [...] }
* ```
*/
export function ed25519PairFromString (value: string): Keypair {
return ed25519PairFromSeed(
blake2AsU8a(
stringToU8a(value)
)
);
}
@@ -0,0 +1,40 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { stringToU8a } from '@pezkuwi/util';
import { waitReady } from '@pezkuwi/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)
);
});
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2017-2025 @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 '@pezkuwi/util';
import { ed25519Sign as wasmSign, isReady } from '@pezkuwi/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 '@pezkuwi/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);
}
@@ -0,0 +1,84 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { stringToU8a } from '@pezkuwi/util';
import { waitReady } from '@pezkuwi/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/);
});
});
}
});
@@ -0,0 +1,41 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ed25519 } from '@noble/curves/ed25519';
import { hasBigInt, u8aToU8a } from '@pezkuwi/util';
import { ed25519Verify as wasmVerify, isReady } from '@pezkuwi/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 '@pezkuwi/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;
}
}
@@ -0,0 +1,59 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { keccakAsU8a } from '../keccak/index.js';
import { ethereumEncode } from './index.js';
describe('formatAddress', () => {
describe('address to address encoding', (): void => {
const ADDRESS = '0x00a329c0648769A73afAc7F9381E08FB43dBEA72';
it('returns 0x for no address', () => {
expect(ethereumEncode()).toBe('0x');
});
it('returns fails on invalid address', () => {
expect(
() => ethereumEncode('0xnotaddress')
).toThrow(/Invalid address or publicKey provided/);
});
it('converts lowercase to the checksummed address', () => {
expect(ethereumEncode(ADDRESS.toLowerCase())).toBe(ADDRESS);
});
it('converts uppercase to the checksummed address', () => {
expect(ethereumEncode(ADDRESS.toUpperCase().replace('0X', '0x'))).toBe(ADDRESS);
});
it('returns formatted address on checksum input', () => {
expect(ethereumEncode(ADDRESS)).toBe(ADDRESS);
});
});
describe('from publicKey', (): void => {
const ADDRESS = '0x4119b2e6c3Cb618F4f0B93ac77f9BeeC7FF02887';
it('encodes a compressed publicKey', (): void => {
expect(
ethereumEncode('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
).toEqual(ADDRESS);
});
it('encodes an expanded publicKey', (): void => {
expect(
ethereumEncode('0x04b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
).toEqual(ADDRESS);
});
it('encodes a pre-hashed key', (): void => {
expect(
ethereumEncode(
keccakAsU8a('0xb9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb1307763fe926c273235fd979a134076d00fd1683cbd35868cb485d4a3a640e52184af')
)
).toEqual(ADDRESS);
});
});
});
@@ -0,0 +1,39 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { HexString } from '@pezkuwi/util/types';
import { u8aToHex, u8aToU8a } from '@pezkuwi/util';
import { keccakAsU8a } from '../keccak/index.js';
import { secp256k1Expand } from '../secp256k1/index.js';
function getH160 (u8a: Uint8Array): Uint8Array {
if ([33, 65].includes(u8a.length)) {
u8a = keccakAsU8a(secp256k1Expand(u8a));
}
return u8a.slice(-20);
}
export function ethereumEncode (addressOrPublic?: string | Uint8Array): HexString {
if (!addressOrPublic) {
return '0x';
}
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`);
}
const address = u8aToHex(getH160(u8aAddress), -1, false);
const hash = u8aToHex(keccakAsU8a(address), -1, false);
let result = '';
for (let i = 0; i < 40; i++) {
result = `${result}${parseInt(hash[i], 16) > 7 ? address[i].toUpperCase() : address[i]}`;
}
return `0x${result}`;
}
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { ethereumEncode } from './encode.js';
export { isEthereumAddress } from './isAddress.js';
export { isEthereumChecksum } from './isChecksum.js';
@@ -0,0 +1,34 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { isEthereumAddress } from './index.js';
const ADDRESS = '0x00a329c0648769A73afAc7F9381E08FB43dBEA72';
describe('isEthereumAddress', () => {
it('returns true when fully lowercase', () => {
expect(isEthereumAddress(ADDRESS.toLowerCase())).toBe(true);
});
it('returns true when fully uppercase', () => {
expect(isEthereumAddress(ADDRESS.toUpperCase().replace('0X', '0x'))).toBe(true);
});
it('returns true when checksummed', () => {
expect(isEthereumAddress(ADDRESS)).toBe(true);
});
it('returns false when empty address', () => {
expect(isEthereumAddress()).toBe(false);
});
it('returns false when invalid address', () => {
expect(isEthereumAddress('0xinvalid')).toBe(false);
});
it('returns false when invalid address of correct length', () => {
expect(isEthereumAddress('0xinvalid000123456789012345678901234567890')).toBe(false);
});
});
@@ -0,0 +1,16 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { isHex } from '@pezkuwi/util';
import { isEthereumChecksum } from './isChecksum.js';
export function isEthereumAddress (address?: string): boolean {
if (!address || address.length !== 42 || !isHex(address)) {
return false;
} else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) {
return true;
}
return isEthereumChecksum(address);
}
@@ -0,0 +1,27 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { u8aToHex } from '@pezkuwi/util';
import { keccakAsU8a } from '../keccak/index.js';
function isInvalidChar (char: string, byte: number): boolean {
return char !== (
byte > 7
? char.toUpperCase()
: char.toLowerCase()
);
}
export function isEthereumChecksum (_address: string): boolean {
const address = _address.replace('0x', '');
const hash = u8aToHex(keccakAsU8a(address.toLowerCase()), -1, false);
for (let i = 0; i < 40; i++) {
if (isInvalidChar(address[i], parseInt(hash[i], 16))) {
return false;
}
}
return true;
}
@@ -0,0 +1,30 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { isEthereumChecksum } from './index.js';
const ADDRESS = '0x00a329c0648769A73afAc7F9381E08FB43dBEA72';
describe('isEthereumChecksum', () => {
it('returns false on invalid address', () => {
expect(isEthereumChecksum('0x00a329c0648769')).toBe(false);
});
it('returns false on non-checksum address', () => {
expect(isEthereumChecksum('0x1234567890abcdeedcba1234567890abcdeedcba')).toBe(false);
});
it('returns false when fully lowercase', () => {
expect(isEthereumChecksum(ADDRESS.toLowerCase())).toBe(false);
});
it('returns false when fully uppercase', () => {
expect(isEthereumChecksum(ADDRESS.toUpperCase().replace('0X', '0x'))).toBe(false);
});
it('returns true on a checksummed address', () => {
expect(isEthereumChecksum(ADDRESS)).toBe(true);
});
});
@@ -0,0 +1,54 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { mnemonicToLegacySeed } from '@pezkuwi/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);
});
});
@@ -0,0 +1,69 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Keypair } from '../../types.js';
import { bnToU8a, stringToU8a, u8aConcat } from '@pezkuwi/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;
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright 2017-2025 @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';
@@ -0,0 +1,34 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { BN_EIGHT, bnToU8a, u8aConcat, u8aToBn } from '@pezkuwi/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)
);
}
@@ -0,0 +1,64 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { u8aToHex } from '@pezkuwi/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);
});
});
});
});
@@ -0,0 +1,42 @@
// Copyright 2017-2025 @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));
}
@@ -0,0 +1,19 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { u8aToHex } from '@pezkuwi/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);
});
});
@@ -0,0 +1,26 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { u8aConcat } from '@pezkuwi/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);
}
@@ -0,0 +1,30 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
/// <reference types="@polkadot/dev-test/globals.d.ts" />
import { hdValidatePath } from './index.js';
const VECTORS: [string, boolean][] = [
["m/44'/60'/0'/0/0", true],
['m/0', true],
["m/123'", true],
["n/44'/60'/0'/0/0", false],
['m', false],
['m/', false],
["m/xyz'", false],
['m/123x', false],
['m/123"', false],
["m/123''", false],
["m/123'0'", false],
[`m/${0x80000000}`, false],
['m/-1', false]
];
describe('hdValidatePath', (): void => {
VECTORS.forEach(([path, result]): void => {
it(`validates ${path} as ${result.toString()}`, (): void => {
expect(hdValidatePath(path)).toEqual(result);
});
});
});
@@ -0,0 +1,24 @@
// Copyright 2017-2025 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0
export const HARDENED = 0x80000000;
export function hdValidatePath (path: string): boolean {
if (!path.startsWith('m/')) {
return false;
}
const parts = path.split('/').slice(1);
for (const p of parts) {
const n = /^\d+'?$/.test(p)
? parseInt(p.replace(/'$/, ''), 10)
: Number.NaN;
if (isNaN(n) || (n >= HARDENED) || (n < 0)) {
return false;
}
}
return true;
}

Some files were not shown because too many files have changed in this diff Show More