diff --git a/package-lock.json b/package-lock.json index 2ec722db..bc59fea5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12965,7 +12965,7 @@ }, "packages/algo-ts/dist": { "name": "@algorandfoundation/algorand-typescript", - "version": "0.0.1-alpha.1", + "version": "0.0.1-alpha.5", "dev": true, "peerDependencies": { "tslib": "^2.6.2" diff --git a/packages/algo-ts/package.json b/packages/algo-ts/package.json index 2379f2ac..f86528a9 100644 --- a/packages/algo-ts/package.json +++ b/packages/algo-ts/package.json @@ -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", diff --git a/packages/algo-ts/src/impl/base-32.ts b/packages/algo-ts/src/impl/base-32.ts index e22b60d3..1d345e75 100644 --- a/packages/algo-ts/src/impl/base-32.ts +++ b/packages/algo-ts/src/impl/base-32.ts @@ -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] diff --git a/packages/algo-ts/src/impl/encoding-util.ts b/packages/algo-ts/src/impl/encoding-util.ts index 151a1893..bcec8bbd 100644 --- a/packages/algo-ts/src/impl/encoding-util.ts +++ b/packages/algo-ts/src/impl/encoding-util.ts @@ -1,3 +1,4 @@ +import { Buffer } from 'node:buffer' import { TextDecoder } from 'node:util' import { AvmError } from './errors' @@ -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) diff --git a/packages/algo-ts/src/impl/primitives.ts b/packages/algo-ts/src/impl/primitives.ts index d7931325..0636d525 100644 --- a/packages/algo-ts/src/impl/primitives.ts +++ b/packages/algo-ts/src/impl/primitives.ts @@ -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' @@ -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 { diff --git a/src/util/base-32.spec.ts b/src/util/base-32.spec.ts new file mode 100644 index 00000000..af9359bf --- /dev/null +++ b/src/util/base-32.spec.ts @@ -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) + }) + }) +}) diff --git a/src/util/base-32.ts b/src/util/base-32.ts index e22b60d3..1d345e75 100644 --- a/src/util/base-32.ts +++ b/src/util/base-32.ts @@ -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] diff --git a/src/util/index.ts b/src/util/index.ts index 35b88bdb..1c1cae6a 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -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 => {