Skip to content
Merged
Next Next commit
Allow double-byte ss58
  • Loading branch information
jacogr committed Feb 3, 2021
commit 1c32741ff2702b56e8f58e8f688907b1b9b2849d
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'];
}
12 changes: 9 additions & 3 deletions packages/util-crypto/src/address/checksum.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
// Copyright 2017-2021 @polkadot/util-crypto authors & contributors
// SPDX-License-Identifier: Apache-2.0

import { compactFromU8a } from '@polkadot/util';

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]
: compactFromU8a(decoded)[1].toNumber();
const isPublicKey = [34 + ss58Length, 35 + ss58Length].includes(decoded.length);

// non-publicKeys has 1 byte checksums, else default to 2
const length = decoded.length - (isPublicKey ? 2 : 1);
Expand All @@ -17,5 +23,5 @@ export function checkAddressChecksum (decoded: Uint8Array): [boolean, number] {
? 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];
}
16 changes: 12 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,14 @@ describe('decodeAddress', (): void => {
);
});

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

it.skip('allows invalid prefix (in list)', (): void => {
expect(
(): Uint8Array => decodeAddress('6GfvWUvHvU8otbZ7sFhXH4eYeMcKdUkL61P3nFy52efEPVUx')
Expand Down
12 changes: 6 additions & 6 deletions packages/util-crypto/src/address/decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export function decodeAddress (encoded: string | Uint8Array, ignoreChecksum?: bo
// assert(defaults.allowedPrefix.includes(decoded[0] as Prefix), error('Invalid decoded address prefix'));
assert(defaults.allowedEncodedLengths.includes(decoded.length), wrapError('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);
// TODO Unless it is an "use everywhere" prefix, throw an error
if (ss58Format !== -1 && (ss58Decoded !== ss58Format)) {
console.log(`WARN: Expected ssPrefix ${ss58Format}, received ${ss58Decoded}`);
}

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

return decoded.slice(1, endPos);
return decoded.slice(ss58Length, endPos);
}
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
};
26 changes: 16 additions & 10 deletions packages/util-crypto/src/address/encode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ describe('encode', (): void => {

it('can re-encode an address', (): void => {
expect(
encodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 68)
).toEqual('7sL6eNJj5ZGV5cn3hhV2deRUsivXfBfMH76wCALCqWj1EKzv');
encodeAddress('5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY', 2)
).toEqual('HNZata7iMYWmk5RvZRTiAsSDhV8366zq2YGb3tLH5Upf74F');
});

it('can re-encode an address to Polkadot live', (): void => {
Expand Down Expand Up @@ -45,38 +45,44 @@ describe('encode', (): void => {
it('encodes a 1-byte address (with prefix)', (): void => {
expect(
encodeAddress(
new Uint8Array([1]), 68
new Uint8Array([1]), 2
)
).toEqual('PqtB');
).toEqual('g4b');
});

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

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

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

it('encodes an 33-byte address', (): void => {
expect(
encodeAddress('0x03b9dc646dd71118e5f7fda681ad9eca36eb3ee96f344f582fbe7b5bcdebb13077')
).toEqual('KWCv1L3QX9LDPwY4VzvLmarEmXjVJidUzZcinvVnmxAJJCBou');
});

it('encodes with 2 byte prefix', (): void => {
expect(
encodeAddress(keyring.alice.publicKey, 255)
).toEqual('2vRvjTMnza9uQZzYcjtEHiYkUzLaUvfXxA5nvU2qC68YUvS9VD');
});
});
9 changes: 7 additions & 2 deletions packages/util-crypto/src/address/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { Prefix } from './types';

// Original implementation: https://github.com/paritytech/polka-ui/blob/4858c094684769080f5811f32b081dd7780b0880/src/polkadot.js#L34
import { assert, u8aConcat } from '@polkadot/util';
import { assert, bnToBn, bnToU8a, u8aConcat } from '@polkadot/util';

import { base58Encode } from '../base58/encode';
import { decodeAddress } from './decode';
Expand All @@ -18,7 +18,12 @@ export function encodeAddress (_key: Uint8Array | string, ss58Format: Prefix = d
assert(defaults.allowedDecodedLengths.includes(key.length), `Expected a valid key to convert, with length ${defaults.allowedDecodedLengths.join(', ')}`);

const isPublicKey = [32, 33].includes(key.length);
const input = u8aConcat(new Uint8Array([ss58Format]), key);
const input = u8aConcat(
ss58Format < 64
? new Uint8Array([ss58Format])
: bnToU8a(bnToBn(ss58Format).shln(2).addn(0b01), { bitLength: 16, isLe: true }),
key
);
const hash = sshash(input);

return base58Encode(
Expand Down
2 changes: 1 addition & 1 deletion packages/util-crypto/src/address/eq.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('addressEq', (): void => {
expect(
addressEq(
'5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY',
'7sL6eNJj5ZGV5cn3hhV2deRUsivXfBfMH76wCALCqWj1EKzv'
'15oF4uVJwmo4TdGW7VfQxNLavjCXviqxT9S1MgbjMNHr6Sp5'
)
).toEqual(true);
});
Expand Down
4 changes: 2 additions & 2 deletions packages/util-crypto/src/address/setSS58Format.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { encodeAddress, setSS58Format } from '.';

describe('setSS58Format', (): void => {
beforeEach((): void => {
setSS58Format(68);
setSS58Format(2);
});

it('sets and allows encoding using', (): void => {
expect(
encodeAddress(
new Uint8Array([1])
)
).toEqual('PqtB');
).toEqual('g4b');
});
});