From ff7a917e32c8db220d3a553603e8b1d6310e0e89 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 1 Feb 2023 17:06:24 -0500 Subject: [PATCH] chore: misc ss58 cleanup (#539) --- effects/extrinsic.ts | 3 +- util/error.ts | 10 ++++++ util/ss58.test.ts | 17 ++++------ util/ss58.ts | 79 ++++++++++++++++++++------------------------ 4 files changed, 55 insertions(+), 54 deletions(-) diff --git a/effects/extrinsic.ts b/effects/extrinsic.ts index 8bb04714b..2c52b98c2 100644 --- a/effects/extrinsic.ts +++ b/effects/extrinsic.ts @@ -99,7 +99,7 @@ export class SignedExtrinsic< const senderSs58 = Z.ls(addrPrefix, this.props.sender).next(([addrPrefix, sender]) => { switch (sender.type) { case "Id": { - return U.ss58.encode(addrPrefix, sender.value) + return U.returnThrows()(() => U.ss58.encode(addrPrefix, sender.value)) } default: { unimplemented() @@ -155,6 +155,7 @@ export function extrinsicsDecoded>(client: Client ) } +// TODO: ensure that ss58 encoding failure is represented in `U` of to-be `ExtrinsicRune` function $extrinsic< Client extends Z.$ = Z.$, Rest extends [sign?: Z.$] = [sign?: Z.$], diff --git a/util/error.ts b/util/error.ts index 8e5457222..1cb2f0546 100644 --- a/util/error.ts +++ b/util/error.ts @@ -4,3 +4,13 @@ export function throwIfError(value: T): Exclude { } return value as Exclude } + +export function returnThrows() { + return (run: () => R): R | Throw => { + try { + return run() + } catch (e) { + return e as Throw + } + } +} diff --git a/util/ss58.test.ts b/util/ss58.test.ts index b38e6c110..116069cbf 100644 --- a/util/ss58.test.ts +++ b/util/ss58.test.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertInstanceOf } from "../deps/std/testing/asserts.ts" +import { assertEquals, assertThrows } from "../deps/std/testing/asserts.ts" import * as ss58 from "./ss58.ts" import { alice } from "./test_pairs.ts" @@ -32,27 +32,24 @@ for ( } Deno.test("ss58.encode invalid public key length", () => { - assertInstanceOf( - ss58.encode(0, alice.publicKey.slice(0, 30)), - ss58.InvalidPublicKeyLengthError, - ) + assertThrows(() => ss58.encode(0, alice.publicKey.slice(0, 30)), ss58.InvalidPublicKeyLengthError) }) Deno.test("ss58.encode invalid network prefix", () => { - assertInstanceOf(ss58.encode(46, alice.publicKey, [0]), ss58.InvalidNetworkPrefixError) + assertThrows(() => ss58.encode(46, alice.publicKey, [0]), ss58.InvalidNetworkPrefixError) }) Deno.test("ss58.decodeRaw long address", () => { - assertInstanceOf(ss58.decodeRaw(new Uint8Array(40)), ss58.InvalidAddressLengthError) + assertThrows(() => ss58.decodeRaw(new Uint8Array(40)), ss58.InvalidAddressLengthError) }) Deno.test("ss58.decodeRaw short address", () => { - assertInstanceOf(ss58.decodeRaw(new Uint8Array(30)), ss58.InvalidAddressLengthError) + assertThrows(() => ss58.decodeRaw(new Uint8Array(30)), ss58.InvalidAddressLengthError) }) Deno.test("ss58.decodeRaw invalid checksum", () => { - assertInstanceOf( - ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)), + assertThrows( + () => ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)), ss58.InvalidAddressChecksumError, ) }) diff --git a/util/ss58.ts b/util/ss58.ts index 38576ee50..bc2cbd00c 100644 --- a/util/ss58.ts +++ b/util/ss58.ts @@ -1,45 +1,27 @@ import * as base58 from "../deps/std/encoding/base58.ts" import { Blake2b } from "../deps/wat_the_crypto.ts" -// SS58PRE string (0x53533538505245 hex) encoded as Uint8Array -const SS58PRE = Uint8Array.of(83, 83, 53, 56, 80, 82, 69) -const CHECKSUM_LENGTH = 2 -const VALID_ADDRESS_LENGTHS: Record = { - 35: true, - 36: true, - 37: true, - 38: true, -} -const VALID_PUBLIC_KEY_LENGTHS: Record = { - 32: true, - 33: true, -} - -export const encode = ( +export function encode( prefix: number, pubKey: Uint8Array, validNetworkPrefixes?: readonly number[], -) => { - const encodeRawResult = encodeRaw(prefix, pubKey, validNetworkPrefixes) - if (encodeRawResult instanceof Error) return encodeRawResult - return base58.encode(encodeRawResult) +): string { + return base58.encode(encodeRaw(prefix, pubKey, validNetworkPrefixes)) } -export const encodeRaw = ( + +export function encodeRaw( prefix: number, pubKey: Uint8Array, validNetworkPrefixes?: readonly number[], -): Uint8Array | InvalidPublicKeyLengthError | InvalidNetworkPrefixError => { +): Uint8Array { const isValidPublicKeyLength = !!VALID_PUBLIC_KEY_LENGTHS[pubKey.length] if (!isValidPublicKeyLength) { - return new InvalidPublicKeyLengthError() + throw new InvalidPublicKeyLengthError() } const isValidNetworkPrefix = !validNetworkPrefixes || validNetworkPrefixes.includes(prefix) - - if (!isValidNetworkPrefix) { - return new InvalidNetworkPrefixError() - } + if (!isValidNetworkPrefix) throw new InvalidNetworkPrefixError() const prefixBytes = prefix < 64 ? Uint8Array.of(prefix) @@ -66,6 +48,8 @@ export const encodeRaw = ( return address } + +export type EncodeError = InvalidPublicKeyLengthError | InvalidNetworkPrefixError export class InvalidPublicKeyLengthError extends Error { override readonly name = "InvalidPublicKeyLengthError" } @@ -73,19 +57,15 @@ export class InvalidNetworkPrefixError extends Error { override readonly name = "InvalidNetworkPrefixError" } -export const decode = (address: string) => decodeRaw(base58.decode(address)) -export const decodeRaw = ( - address: Uint8Array, -): - | [prefix: number, pubKey: Uint8Array] - | InvalidAddressLengthError - | InvalidAddressChecksumError => -{ - const isValidAddressLength = !!VALID_ADDRESS_LENGTHS[address.length] +export type DecodeResult = [prefix: number, pubKey: Uint8Array] - if (!isValidAddressLength) { - return new InvalidAddressLengthError() - } +export function decode(address: string): DecodeResult { + return decodeRaw(base58.decode(address)) +} + +export function decodeRaw(address: Uint8Array): DecodeResult { + const isValidAddressLength = !!VALID_ADDRESS_LENGTHS[address.length] + if (!isValidAddressLength) throw new InvalidAddressLengthError() const prefixLength = address[0]! & 0b0100_0000 ? 2 : 1 @@ -104,19 +84,32 @@ export const decodeRaw = ( hasher.dispose() if (digest[0] !== checksum[0] || digest[1] !== checksum[1]) { - return new InvalidAddressChecksumError() + throw new InvalidAddressChecksumError() } - const pubKey = address.subarray( - prefixLength, - address.length - CHECKSUM_LENGTH, - ) + const pubKey = address.subarray(prefixLength, address.length - CHECKSUM_LENGTH) return [prefix, pubKey] } + +export type DecodeError = InvalidAddressLengthError | InvalidAddressChecksumError export class InvalidAddressLengthError extends Error { override readonly name = "InvalidAddressError" } export class InvalidAddressChecksumError extends Error { override readonly name = "InvalidAddressChecksumError" } + +// SS58PRE string (0x53533538505245 hex) encoded as Uint8Array +const SS58PRE = Uint8Array.of(83, 83, 53, 56, 80, 82, 69) +const CHECKSUM_LENGTH = 2 +const VALID_ADDRESS_LENGTHS: Record = { + 35: true, + 36: true, + 37: true, + 38: true, +} +const VALID_PUBLIC_KEY_LENGTHS: Record = { + 32: true, + 33: true, +}