Skip to content

Commit

Permalink
fix: Bugs in hex decode and b32 encode
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanmenzel committed Oct 9, 2024
1 parent cdce210 commit af18eb0
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 9 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/algo-ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@algorandfoundation/algorand-typescript",
"version": "0.0.1-alpha.4",
"version": "0.0.1-alpha.5",
"description": "This package contains definitions for the types which comprise Algorand TypeScript which can be compiled to run on the Algorand Virtual Machine using the Puya compiler.",
"private": false,
"main": "index.js",
Expand Down
2 changes: 1 addition & 1 deletion packages/algo-ts/src/impl/base-32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const uint8ArrayToBase32 = (value: Uint8Array): string => {

if (allBytes.length < 1) break
base32str += BASE32_ALPHABET[a >>> 3]
base32str += BASE32_ALPHABET[((a << 2) | ((b || 0) >>> 6)) & 32]
base32str += BASE32_ALPHABET[((a << 2) | ((b || 0) >>> 6)) & 31]
if (allBytes.length < 2) break
base32str += BASE32_ALPHABET[(b >>> 1) & 31]
base32str += BASE32_ALPHABET[((b << 4) | (c >>> 4)) & 31]
Expand Down
13 changes: 13 additions & 0 deletions packages/algo-ts/src/impl/encoding-util.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'node:buffer'
import { TextDecoder } from 'node:util'
import { AvmError } from './errors'

Expand All @@ -9,6 +10,18 @@ export const uint8ArrayToBigInt = (v: Uint8Array): bigint => {
.reduce((a, b) => a + b, 0n)
}

export const hexToUint8Array = (value: string): Uint8Array => {
if (value.length % 2 !== 0) {
// TODO: Verify AVM behaviour is to fail
throw new AvmError('Hex string must have even number of characters')
}
return Uint8Array.from(Buffer.from(value, 'hex'))
}

export const base64ToUint8Array = (value: string): Uint8Array => {
return Uint8Array.from(Buffer.from(value, 'base64'))
}

export const bigIntToUint8Array = (val: bigint, fixedSize: number | 'dynamic' = 'dynamic'): Uint8Array => {
if (val === 0n && fixedSize === 'dynamic') {
return new Uint8Array(0)
Expand Down
14 changes: 11 additions & 3 deletions packages/algo-ts/src/impl/primitives.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import type { biguint, BigUintCompat, bytes, BytesCompat, uint64, Uint64Compat } from '../index'
import { DeliberateAny } from '../typescript-helpers'
import { base32ToUint8Array } from './base-32'
import { bigIntToUint8Array, uint8ArrayToBigInt, uint8ArrayToHex, uint8ArrayToUtf8, utf8ToUint8Array } from './encoding-util'
import {
base64ToUint8Array,
bigIntToUint8Array,
hexToUint8Array,
uint8ArrayToBigInt,
uint8ArrayToHex,
uint8ArrayToUtf8,
utf8ToUint8Array,
} from './encoding-util'
import { avmError, AvmError, internalError } from './errors'
import { nameOfType } from './name-of-type'

Expand Down Expand Up @@ -307,11 +315,11 @@ export class BytesCls extends AlgoTsPrimitiveCls {
}

static fromHex(hex: string): BytesCls {
return new BytesCls(Uint8Array.from(Buffer.from(hex, 'hex')))
return new BytesCls(hexToUint8Array(hex))
}

static fromBase64(b64: string): BytesCls {
return new BytesCls(Uint8Array.from(Buffer.from(b64, 'base64')))
return new BytesCls(base64ToUint8Array(b64))
}

static fromBase32(b32: string): BytesCls {
Expand Down
31 changes: 31 additions & 0 deletions src/util/base-32.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, expect, it } from 'vitest'
import { base32ToUint8Array, uint8ArrayToBase32 } from './base-32'
import { hexToUint8Array } from './index'

describe('base-32 encoding', () => {
const ZERO_ADDRESS = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ'
it(`Zero address should be ${ZERO_ADDRESS}`, () => {
const publicKey = new Uint8Array(32)
const hash = hexToUint8Array('0C74E554')
const zeroAddressBytes = new Uint8Array([...publicKey, ...hash])
//const zeroAddressBytes = hexToUint8Array('00000000000000000000000000000000000000000000000000000000000000000c74e554')
const addressStr = uint8ArrayToBase32(zeroAddressBytes)
expect(addressStr).toBe(ZERO_ADDRESS)
})

describe('encode and decode should return same value', () => {
it.each([
[new Uint8Array()],
[new Uint8Array([1])],
[new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])],
[new Uint8Array(32)],
[new Uint8Array(36)],
])('%s', (value) => {
const encoded = uint8ArrayToBase32(value)

const decoded = base32ToUint8Array(encoded)

expect(decoded).toStrictEqual(value)
})
})
})
2 changes: 1 addition & 1 deletion src/util/base-32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const uint8ArrayToBase32 = (value: Uint8Array): string => {

if (allBytes.length < 1) break
base32str += BASE32_ALPHABET[a >>> 3]
base32str += BASE32_ALPHABET[((a << 2) | ((b || 0) >>> 6)) & 32]
base32str += BASE32_ALPHABET[((a << 2) | ((b || 0) >>> 6)) & 31]
if (allBytes.length < 2) break
base32str += BASE32_ALPHABET[(b >>> 1) & 31]
base32str += BASE32_ALPHABET[((b << 4) | (c >>> 4)) & 31]
Expand Down
4 changes: 2 additions & 2 deletions src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ export const uint8ArrayToBase64 = (value: Uint8Array): string => Buffer.from(val

export const hexToUint8Array = (value: string): Uint8Array => {
invariant(value.length % 2 === 0, 'Hex string must have even number of characters')
return new Uint8Array(new Array(value.length / 2).fill(0).map((_, i) => parseInt(value.slice(i * 2, i * 2 + 1), 16)))
return Uint8Array.from(Buffer.from(value, 'hex'))
}

export const base64ToUint8Array = (value: string): Uint8Array => {
return new Uint8Array(Buffer.from(value, 'base64'))
return Uint8Array.from(Buffer.from(value, 'base64'))
}

export const utf8ToUint8Array = (value: string): Uint8Array => {
Expand Down

0 comments on commit af18eb0

Please sign in to comment.