From f7ff05753d9bc39b31bdd4e7f893ee04cab77823 Mon Sep 17 00:00:00 2001 From: Janek Rahrt Date: Wed, 1 Dec 2021 12:28:53 +0100 Subject: [PATCH] feat(utils): support shortstring and uint256 --- __tests__/utils/shortString.test.ts | 22 ++++++++++++++++++++ __tests__/utils/uint256.test.ts | 32 +++++++++++++++++++++++++++++ src/constants.ts | 10 ++++----- src/index.ts | 2 ++ src/utils/shortString.ts | 21 +++++++++++++++++++ src/utils/uint256.ts | 32 +++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 __tests__/utils/shortString.test.ts create mode 100644 __tests__/utils/uint256.test.ts create mode 100644 src/utils/shortString.ts create mode 100644 src/utils/uint256.ts diff --git a/__tests__/utils/shortString.test.ts b/__tests__/utils/shortString.test.ts new file mode 100644 index 000000000..e6262e010 --- /dev/null +++ b/__tests__/utils/shortString.test.ts @@ -0,0 +1,22 @@ +import { decodeShortString, encodeShortString } from '../../src/utils/shortString'; + +describe('shortString', () => { + test('should convert string to number', () => { + expect(encodeShortString('hello')).toMatchInlineSnapshot(`"0x68656c6c6f"`); + }); + test('should convert number to string', () => { + expect(decodeShortString('0x68656c6c6f')).toMatchInlineSnapshot(`"hello"`); + }); + test('should throw if string is too long', () => { + expect(() => + encodeShortString('hello world hello world hello world hello world hello world hello world') + ).toThrowErrorMatchingInlineSnapshot( + `"hello world hello world hello world hello world hello world hello world is too long"` + ); + }); + test('should throw if string contains non ascii chars', () => { + expect(() => encodeShortString('hello\uD83D\uDE00')).toThrowErrorMatchingInlineSnapshot( + `"hello😀 is not an ASCII string"` + ); + }); +}); diff --git a/__tests__/utils/uint256.test.ts b/__tests__/utils/uint256.test.ts new file mode 100644 index 000000000..eef19f41d --- /dev/null +++ b/__tests__/utils/uint256.test.ts @@ -0,0 +1,32 @@ +import { ONE } from '../../src/constants'; +import { toBN } from '../../src/utils/number'; +import { UINT_128_MAX, UINT_256_MAX, bnToUint256, uint256ToBN } from '../../src/utils/uint256'; + +describe('cairo uint256', () => { + test('should convert 0 from BN to uint256 struct', () => { + const uint256 = bnToUint256('0'); + expect(uint256).toMatchInlineSnapshot(` + Object { + "high": "0x0", + "low": "0x0", + } + `); + }); + test('should convert 0 from uint256 to BN', () => { + expect(uint256ToBN({ low: '0x0', high: '0x0' }).toString()).toMatchInlineSnapshot(`"0"`); + }); + test('should convert BN over 2^128 to uint256 struct', () => { + const uint256 = bnToUint256(UINT_128_MAX.add(ONE)); + expect(uint256).toMatchInlineSnapshot(` + Object { + "high": "0x1", + "low": "0x0", + } + `); + }); + test('should throw if BN over uint256 range', () => { + expect(() => bnToUint256(UINT_256_MAX.add(toBN(1)))).toThrowErrorMatchingInlineSnapshot( + `"Number is too large"` + ); + }); +}); diff --git a/src/constants.ts b/src/constants.ts index 53aba6c75..9e2e0b638 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,13 +1,11 @@ -import BN from 'bn.js'; - import { toBN } from './utils/number'; export { IS_BROWSER } from './utils/encode'; -export const ZERO: BN = toBN(0); -export const ONE: BN = toBN(1); -export const TWO: BN = toBN(2); -export const MASK_250: BN = TWO.pow(toBN(250)).sub(ONE); // 2 ** 250 - 1 +export const ZERO = toBN(0); +export const ONE = toBN(1); +export const TWO = toBN(2); +export const MASK_250 = TWO.pow(toBN(250)).sub(ONE); // 2 ** 250 - 1 /** * The following is taken from https://github.com/starkware-libs/starkex-resources/blob/master/crypto/starkware/crypto/signature/pedersen_params.json but converted to hex, because JS is very bad handling big integers by default diff --git a/src/index.ts b/src/index.ts index e09574f53..ff41a4340 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,3 +16,5 @@ export * as json from './utils/json'; export * as number from './utils/number'; export * as stark from './utils/stark'; export * as ec from './utils/ellipticCurve'; +export * as uint256 from './utils/uint256'; +export * as shortString from './utils/shortString'; diff --git a/src/utils/shortString.ts b/src/utils/shortString.ts new file mode 100644 index 000000000..c597a6f2d --- /dev/null +++ b/src/utils/shortString.ts @@ -0,0 +1,21 @@ +import { addHexPrefix, removeHexPrefix } from './encode'; + +export function isASCII(str: string) { + // eslint-disable-next-line no-control-regex + return /^[\x00-\x7F]*$/.test(str); +} + +// function to check if string has less or equal 31 characters +export function isShortString(str: string) { + return str.length <= 31; +} + +export function encodeShortString(str: string) { + if (!isASCII(str)) throw new Error(`${str} is not an ASCII string`); + if (!isShortString(str)) throw new Error(`${str} is too long`); + return addHexPrefix(str.replace(/./g, (char) => char.charCodeAt(0).toString(16))); +} + +export function decodeShortString(str: string) { + return removeHexPrefix(str).replace(/.{2}/g, (hex) => String.fromCharCode(parseInt(hex, 16))); +} diff --git a/src/utils/uint256.ts b/src/utils/uint256.ts new file mode 100644 index 000000000..d3b9dae12 --- /dev/null +++ b/src/utils/uint256.ts @@ -0,0 +1,32 @@ +import { addHexPrefix } from './encode'; +import { BigNumberish, toBN } from './number'; + +// Represents an integer in the range [0, 2^256). +export interface Uint256 { + // The low 128 bits of the value. + low: BigNumberish; + // The high 128 bits of the value. + high: BigNumberish; +} + +// function to convert Uint256 to BN +export function uint256ToBN(uint256: Uint256) { + return toBN(uint256.high).shln(128).add(toBN(uint256.low)); +} + +export const UINT_128_MAX = toBN(1).shln(128).sub(toBN(1)); +export const UINT_256_MAX = toBN(1).shln(256).sub(toBN(1)); +// function to check if BN is smaller or equal 2**256-1 +export function isUint256(bn: BigNumberish): boolean { + return toBN(bn).lte(UINT_256_MAX); +} + +// function to convert BN to Uint256 +export function bnToUint256(bignumber: BigNumberish): Uint256 { + const bn = toBN(bignumber); + if (!isUint256(bn)) throw new Error('Number is too large'); + return { + low: addHexPrefix(bn.maskn(128).toString(16)), + high: addHexPrefix(bn.shrn(128).toString(16)), + }; +}