mirror of
https://github.com/pezkuwichain/pezkuwi-common.git
synced 2026-04-22 02:07:56 +00:00
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:
@@ -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';
|
||||
```
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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'
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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/);
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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 "="/);
|
||||
});
|
||||
});
|
||||
@@ -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==');
|
||||
});
|
||||
});
|
||||
@@ -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==');
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
@@ -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';
|
||||
@@ -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 };
|
||||
@@ -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';
|
||||
@@ -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
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user