Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
chore: misc ss58 cleanup (#539)
Browse files Browse the repository at this point in the history
  • Loading branch information
harrysolovay authored Feb 1, 2023
1 parent bb30e38 commit ff7a917
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 54 deletions.
3 changes: 2 additions & 1 deletion effects/extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.EncodeError>()(() => U.ss58.encode(addrPrefix, sender.value))
}
default: {
unimplemented()
Expand Down Expand Up @@ -155,6 +155,7 @@ export function extrinsicsDecoded<Client extends Z.$<rpc.Client>>(client: Client
)
}

// TODO: ensure that ss58 encoding failure is represented in `U` of to-be `ExtrinsicRune`
function $extrinsic<
Client extends Z.$<rpc.Client> = Z.$<rpc.Client>,
Rest extends [sign?: Z.$<Signer>] = [sign?: Z.$<Signer>],
Expand Down
10 changes: 10 additions & 0 deletions util/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@ export function throwIfError<T>(value: T): Exclude<T, Error> {
}
return value as Exclude<T, Error>
}

export function returnThrows<Throw>() {
return <R>(run: () => R): R | Throw => {
try {
return run()
} catch (e) {
return e as Throw
}
}
}
17 changes: 7 additions & 10 deletions util/ss58.test.ts
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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,
)
})
79 changes: 36 additions & 43 deletions util/ss58.ts
Original file line number Diff line number Diff line change
@@ -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<number, boolean | undefined> = {
35: true,
36: true,
37: true,
38: true,
}
const VALID_PUBLIC_KEY_LENGTHS: Record<number, boolean | undefined> = {
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)
Expand All @@ -66,26 +48,24 @@ export const encodeRaw = (

return address
}

export type EncodeError = InvalidPublicKeyLengthError | InvalidNetworkPrefixError
export class InvalidPublicKeyLengthError extends Error {
override readonly name = "InvalidPublicKeyLengthError"
}
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

Expand All @@ -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<number, boolean | undefined> = {
35: true,
36: true,
37: true,
38: true,
}
const VALID_PUBLIC_KEY_LENGTHS: Record<number, boolean | undefined> = {
32: true,
33: true,
}

0 comments on commit ff7a917

Please sign in to comment.