Skip to content
Merged
12 changes: 6 additions & 6 deletions packages/keyring/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ describe('keypair', (): void => {

it('adds from a mnemonic, with correct ss58', (): void => {
setSS58Format(20); // this would not be used
keyring.setSS58Format(68); // this would be used
keyring.setSS58Format(2); // this would be used

const pair = keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {});

expect(pair.address).toEqual('7sPsxWPE5DzAyPT3VuoJYw5NTGscx9QYN9oddQx4kALKC3hH');
expect(pair.address).toEqual('HSLu2eci2GCfWkRimjjdTXKoFSDL3rBv5Ey2JWCBj68cVZj');
expect(encodeAddress(pair.publicKey)).toEqual('35cDYtPsdG1HUa2n2MaARgJyRz1WKMBZK1DL6c5cX7nugQh1');
});

Expand Down Expand Up @@ -123,11 +123,11 @@ describe('keypair', (): void => {
});

it('adds from a mnemonic', (): void => {
keyring.setSS58Format(68);
keyring.setSS58Format(2);

expect(
keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card', {}).address
).toEqual('7qQGarA4PWjPPVHG4USn1yuuVZvEHN7XZz8o7EbAp48jayZQ');
).toEqual('FSjXNRT2K1R5caeHLPD6WMrqYUpfGZB7ua8W89JFctZ1YqV');
});

it('allows publicKeys retrieval', (): void => {
Expand Down Expand Up @@ -200,11 +200,11 @@ describe('keypair', (): void => {
});

it('adds from a mnemonic', (): void => {
keyring.setSS58Format(68);
keyring.setSS58Format(2);

expect(
keyring.addFromMnemonic('moral movie very draw assault whisper awful rebuild speed purity repeat card').address
).toEqual('7ooxHV3mz4nnWbK8v7Mxcb71QMpof268eL1A2VrYWUNWJk8P');
).toEqual('DrRE1KAcs4pCicX8yJPh7YxkLPQ2vXnCFSVRPQfx38KjEFe');
});

it('allows publicKeys retrieval', (): void => {
Expand Down
10 changes: 3 additions & 7 deletions packages/keyring/src/pair/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,11 @@ describe('pair', (): void => {
});

it('allows encoding of address with different prefixes', (): void => {
expect(keyring.alice.address).toEqual(
'5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua'
);
expect(keyring.alice.address).toEqual('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua');

setSS58Format(68);
setSS58Format(255);

expect(keyring.alice.address).toEqual(
'7sGUeMak588SPY2YMmmuKUuLz7u2WQpf74F9dCFtSLB2td9d'
);
expect(keyring.alice.address).toEqual('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k');

setSS58Format(42);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/networks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ const UNSORTED = [0, 2, 42];
// last. This make lookups for the current a simple genesisHash[0]
// (See Kusama as an example)

const createReserved = (prefix: number, displayName: string): NetworkFromSubstrate => ({
const createReserved = (prefix: number, displayName: string, network: string | null = null): NetworkFromSubstrate => ({
decimals: null,
displayName,
isIgnored: true,
network: `reserved${prefix}`,
network,
prefix,
standardAccount: null,
symbols: null,
Expand Down Expand Up @@ -436,8 +436,8 @@ const all: NetworkFromSubstrate[] = [
symbols: ['UART', 'UINK'],
website: 'https://uniarts.me'
},
createReserved(46, 'This prefix is reserved.'),
createReserved(47, 'This prefix is reserved.')
createReserved(46, 'This prefix is reserved.', 'reserved46'),
createReserved(47, 'This prefix is reserved.', 'reserved47')
];

// The list of available/claimed prefixes
Expand Down
2 changes: 1 addition & 1 deletion packages/networks/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type Icon = 'beachball' | 'empty' | 'jdenticon' | 'polkadot' | 'substrate
export interface NetworkFromSubstrate {
decimals: number[] | null,
displayName: string;
network: string;
network: string | null;
prefix: number;
genesisHash?: string[] | null;
hasLedgerSupport?: boolean;
Expand Down
8 changes: 4 additions & 4 deletions packages/util-crypto/src/address/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ export function checkAddress (address: string, prefix: Prefix): [boolean, string
return [false, (error as Error).message];
}

if (decoded[0] !== prefix) {
return [false, `Prefix mismatch, expected ${prefix}, found ${decoded[0]}`];
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'];
}

const [isValid] = checkAddressChecksum(decoded);

return [isValid, isValid ? null : 'Invalid decoded address checksum'];
}
25 changes: 25 additions & 0 deletions packages/util-crypto/src/address/checksum.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2017-2021 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { base58Decode } from '../base58/decode';
import { checkAddressChecksum } from './checksum';

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', (): 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]);
});
});
22 changes: 13 additions & 9 deletions packages/util-crypto/src/address/checksum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@

import { sshash } from './sshash';

export function checkAddressChecksum (decoded: Uint8Array): [boolean, number] {
const isPublicKey = [35, 36].includes(decoded.length);
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);

// non-publicKeys has 1 byte checksums, else default to 2
// 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]
);

// see if the hash actually matches
const isValid = isPublicKey
? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1]
: decoded[decoded.length - 1] === hash[0];

return [isValid, length];
return [isValid, length, ss58Length, ss58Decoded];
}
22 changes: 18 additions & 4 deletions packages/util-crypto/src/address/decode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,25 @@ describe('decodeAddress', (): void => {

it('decodes a 1-byte accountId (with prefix)', (): void => {
expect(
decodeAddress('PqtB', false, 68)
decodeAddress('g4b', false, 2)
).toEqual(new Uint8Array([1]));
});

it('decodes a 2-byte accountId', (): void => {
expect(
decodeAddress('2jpAFn', false, 68)
decodeAddress('3xygo', false, 2)
).toEqual(new Uint8Array([0, 1]));
});

it('encodes a 4-byte address', (): void => {
expect(
decodeAddress('as7QnGMf', false, 68)
decodeAddress('zswfoZa', false, 2)
).toEqual(new Uint8Array([1, 2, 3, 4]));
});

it('decodes a 8-byte address', (): void => {
expect(
decodeAddress('4q7qY5RBG7Z4wv', false, 68)
decodeAddress('848Gh2GcGaZia', false, 2)
).toEqual(new Uint8Array([42, 44, 10, 0, 0, 0, 0, 0]));
});

Expand All @@ -81,6 +81,20 @@ describe('decodeAddress', (): void => {
);
});

it('decodes a 2-byte prefix', (): void => {
expect(
decodeAddress('yGHU8YKprxHbHdEv7oUK4rzMZXtsdhcXVG2CAMyC9WhzhjH2k')
).toEqual(
decodeAddress('5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaQua')
);
});

it('decodes a 2-byte prefix (ecdsa, from Substrate)', (): void => {
expect(
u8aToHex(decodeAddress('4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV'))
).toEqual('0x035676109c54b9a16d271abeb4954316a40a32bcce023ac14c8e26e958aa68fba9');
});

it.skip('allows invalid prefix (in list)', (): void => {
expect(
(): Uint8Array => decodeAddress('6GfvWUvHvU8otbZ7sFhXH4eYeMcKdUkL61P3nFy52efEPVUx')
Expand Down
27 changes: 9 additions & 18 deletions packages/util-crypto/src/address/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,23 @@ import { base58Decode } from '../base58/decode';
import { checkAddressChecksum } from './checksum';
import { defaults } from './defaults';

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function decodeAddress (encoded: string | Uint8Array, ignoreChecksum?: boolean, ss58Format: Prefix = -1): Uint8Array {
if (isU8a(encoded) || isHex(encoded)) {
return u8aToU8a(encoded);
}

const wrapError = (message: string) => `Decoding ${encoded as string}: ${message}`;
let decoded;

try {
decoded = base58Decode(encoded);
} catch (error) {
throw new Error(wrapError((error as Error).message));
}
const decoded = base58Decode(encoded);

// assert(defaults.allowedPrefix.includes(decoded[0] as Prefix), error('Invalid decoded address prefix'));
assert(defaults.allowedEncodedLengths.includes(decoded.length), wrapError('Invalid decoded address length'));
assert(defaults.allowedEncodedLengths.includes(decoded.length), 'Invalid decoded address length');

// TODO Unless it is an "use everywhere" prefix, throw an error
// if (ss58Format !== -1 && (decoded[0] !== ss58Format)) {
// console.log(`WARN: Expected ${ss58Format}, found ${decoded[0]}`);
// }
const [isValid, endPos, ss58Length, ss58Decoded] = checkAddressChecksum(decoded);

const [isValid, endPos] = checkAddressChecksum(decoded);
assert(ignoreChecksum || isValid, 'Invalid decoded address checksum');
assert([-1, ss58Decoded].includes(ss58Format), `Expected ss58Format ${ss58Format}, received ${ss58Decoded}`);

assert(ignoreChecksum || isValid, wrapError('Invalid decoded address checksum'));

return decoded.slice(1, endPos);
return decoded.slice(ss58Length, endPos);
} catch (error) {
throw new Error(`Decoding ${encoded as string}: ${(error as Error).message}`);
}
}
2 changes: 1 addition & 1 deletion packages/util-crypto/src/address/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { available } from '@polkadot/networks';
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],
allowedEncodedLengths: [3, 4, 6, 10, 35, 36, 37, 38],
allowedPrefix: available.map(({ prefix }) => prefix),
prefix: 42
};
Loading