From e003511f70036553a7bb0307ad31a5c1751a8ba9 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 31 Jan 2023 19:14:44 -0500 Subject: [PATCH 1/5] misc ss58 cleanup --- effects/extrinsic.ts | 3 +- util/error.ts | 4 +++ util/ss58.ts | 77 ++++++++++++++++++++------------------------ 3 files changed, 41 insertions(+), 43 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..40b22db58 100644 --- a/util/error.ts +++ b/util/error.ts @@ -4,3 +4,7 @@ export function throwIfError(value: T): Exclude { } return value as Exclude } + +export function returnThrows() { + return (run: () => R) => run() as R | Throw +} diff --git a/util/ss58.ts b/util/ss58.ts index 38576ee50..0a4e53803 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 = ( 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, +} From 37c6bd09a5481f84ca2754b86febc7f28ffcc873 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 31 Jan 2023 19:16:33 -0500 Subject: [PATCH 2/5] fix returnThrows util --- util/error.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/util/error.ts b/util/error.ts index 40b22db58..1cb2f0546 100644 --- a/util/error.ts +++ b/util/error.ts @@ -6,5 +6,11 @@ export function throwIfError(value: T): Exclude { } export function returnThrows() { - return (run: () => R) => run() as R | Throw + return (run: () => R): R | Throw => { + try { + return run() + } catch (e) { + return e as Throw + } + } } From 2c68c93b4507d3e4adaeffdd9dbeb7a26110a5b3 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Tue, 31 Jan 2023 19:29:28 -0500 Subject: [PATCH 3/5] fix failing tests --- util/ss58.test.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/util/ss58.test.ts b/util/ss58.test.ts index b38e6c110..7be567f01 100644 --- a/util/ss58.test.ts +++ b/util/ss58.test.ts @@ -1,4 +1,5 @@ import { assertEquals, assertInstanceOf } from "../deps/std/testing/asserts.ts" +import { returnThrows } from "./error.ts" import * as ss58 from "./ss58.ts" import { alice } from "./test_pairs.ts" @@ -33,26 +34,37 @@ for ( Deno.test("ss58.encode invalid public key length", () => { assertInstanceOf( - ss58.encode(0, alice.publicKey.slice(0, 30)), + returnThrows()(() => 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) + assertInstanceOf( + returnThrows()(() => ss58.encode(46, alice.publicKey, [0])), + ss58.InvalidNetworkPrefixError, + ) }) Deno.test("ss58.decodeRaw long address", () => { - assertInstanceOf(ss58.decodeRaw(new Uint8Array(40)), ss58.InvalidAddressLengthError) + assertInstanceOf( + returnThrows()(() => ss58.decodeRaw(new Uint8Array(40))), + ss58.InvalidAddressLengthError, + ) }) Deno.test("ss58.decodeRaw short address", () => { - assertInstanceOf(ss58.decodeRaw(new Uint8Array(30)), ss58.InvalidAddressLengthError) + assertInstanceOf( + returnThrows()(() => ss58.decodeRaw(new Uint8Array(30))), + ss58.InvalidAddressLengthError, + ) }) Deno.test("ss58.decodeRaw invalid checksum", () => { assertInstanceOf( - ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)), + returnThrows()(() => + ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)) + ), ss58.InvalidAddressChecksumError, ) }) From 2455efe1b053b5fc4fdb497a8856d9ad06793f7a Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 1 Feb 2023 14:40:58 -0500 Subject: [PATCH 4/5] t6 feedback --- util/ss58.test.ts | 35 ++++++++++++++++------------------- util/ss58.ts | 4 ++-- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/util/ss58.test.ts b/util/ss58.test.ts index 7be567f01..61a16d431 100644 --- a/util/ss58.test.ts +++ b/util/ss58.test.ts @@ -1,5 +1,4 @@ -import { assertEquals, assertInstanceOf } from "../deps/std/testing/asserts.ts" -import { returnThrows } from "./error.ts" +import { assertEquals, assertThrows } from "../deps/std/testing/asserts.ts" import * as ss58 from "./ss58.ts" import { alice } from "./test_pairs.ts" @@ -33,38 +32,36 @@ for ( } Deno.test("ss58.encode invalid public key length", () => { - assertInstanceOf( - returnThrows()(() => ss58.encode(0, alice.publicKey.slice(0, 30))), - ss58.InvalidPublicKeyLengthError, + assertThrows( + () => ss58.encode(0, alice.publicKey.slice(0, 30)), + ss58.InvalidPublicKeyLengthError.prototype.message, ) }) Deno.test("ss58.encode invalid network prefix", () => { - assertInstanceOf( - returnThrows()(() => ss58.encode(46, alice.publicKey, [0])), - ss58.InvalidNetworkPrefixError, + assertThrows( + () => ss58.encode(46, alice.publicKey, [0]), + ss58.InvalidNetworkPrefixError.prototype.message, ) }) Deno.test("ss58.decodeRaw long address", () => { - assertInstanceOf( - returnThrows()(() => ss58.decodeRaw(new Uint8Array(40))), - ss58.InvalidAddressLengthError, + assertThrows( + () => ss58.decodeRaw(new Uint8Array(40)), + ss58.InvalidAddressLengthError.prototype.message, ) }) Deno.test("ss58.decodeRaw short address", () => { - assertInstanceOf( - returnThrows()(() => ss58.decodeRaw(new Uint8Array(30))), - ss58.InvalidAddressLengthError, + assertThrows( + () => ss58.decodeRaw(new Uint8Array(30)), + ss58.InvalidAddressLengthError.prototype.message, ) }) Deno.test("ss58.decodeRaw invalid checksum", () => { - assertInstanceOf( - returnThrows()(() => - ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)) - ), - ss58.InvalidAddressChecksumError, + assertThrows( + () => ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)), + ss58.InvalidAddressChecksumError.prototype.message, ) }) diff --git a/util/ss58.ts b/util/ss58.ts index 0a4e53803..bc2cbd00c 100644 --- a/util/ss58.ts +++ b/util/ss58.ts @@ -9,11 +9,11 @@ export function encode( return base58.encode(encodeRaw(prefix, pubKey, validNetworkPrefixes)) } -export const encodeRaw = ( +export function encodeRaw( prefix: number, pubKey: Uint8Array, validNetworkPrefixes?: readonly number[], -): Uint8Array => { +): Uint8Array { const isValidPublicKeyLength = !!VALID_PUBLIC_KEY_LENGTHS[pubKey.length] if (!isValidPublicKeyLength) { From 53fce751e3d4cfc8c673558288aa2ffa8d6d1610 Mon Sep 17 00:00:00 2001 From: Harry Solovay Date: Wed, 1 Feb 2023 16:54:26 -0500 Subject: [PATCH 5/5] fix assertThrows to check against ctor not message --- util/ss58.test.ts | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/util/ss58.test.ts b/util/ss58.test.ts index 61a16d431..116069cbf 100644 --- a/util/ss58.test.ts +++ b/util/ss58.test.ts @@ -32,36 +32,24 @@ for ( } Deno.test("ss58.encode invalid public key length", () => { - assertThrows( - () => ss58.encode(0, alice.publicKey.slice(0, 30)), - ss58.InvalidPublicKeyLengthError.prototype.message, - ) + assertThrows(() => ss58.encode(0, alice.publicKey.slice(0, 30)), ss58.InvalidPublicKeyLengthError) }) Deno.test("ss58.encode invalid network prefix", () => { - assertThrows( - () => ss58.encode(46, alice.publicKey, [0]), - ss58.InvalidNetworkPrefixError.prototype.message, - ) + assertThrows(() => ss58.encode(46, alice.publicKey, [0]), ss58.InvalidNetworkPrefixError) }) Deno.test("ss58.decodeRaw long address", () => { - assertThrows( - () => ss58.decodeRaw(new Uint8Array(40)), - ss58.InvalidAddressLengthError.prototype.message, - ) + assertThrows(() => ss58.decodeRaw(new Uint8Array(40)), ss58.InvalidAddressLengthError) }) Deno.test("ss58.decodeRaw short address", () => { - assertThrows( - () => ss58.decodeRaw(new Uint8Array(30)), - ss58.InvalidAddressLengthError.prototype.message, - ) + assertThrows(() => ss58.decodeRaw(new Uint8Array(30)), ss58.InvalidAddressLengthError) }) Deno.test("ss58.decodeRaw invalid checksum", () => { assertThrows( () => ss58.decodeRaw(Uint8Array.of(0, ...alice.publicKey, 255, 255)), - ss58.InvalidAddressChecksumError.prototype.message, + ss58.InvalidAddressChecksumError, ) })