Skip to content

Commit

Permalink
Adjust BE u8aTo{Bn, BigInt, Number} check (#1868)
Browse files Browse the repository at this point in the history
* Adjust BE u8aTo{Bn, BigInt, Numer} check

* Adjust function names (liberal find-and-replace)
  • Loading branch information
jacogr authored Aug 22, 2023
1 parent 3dce4fe commit cf9082e
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 51 deletions.
31 changes: 16 additions & 15 deletions packages/util/src/u8a/toBigInt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,28 @@ const U64_MAX = BigInt('0x10000000000000000');
export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): bigint {
// slice + reverse is expensive, however SCALE is LE by default so this is the path
// we are most interested in (the BE is added for the sake of being comprehensive)
const u8a = isLe
? value
: value.slice().reverse();
const count = u8a.length;
if (!isLe) {
value = value.slice().reverse();
}

const count = value.length;

if (isNegative && count && (u8a[count - 1] & 0x80)) {
if (isNegative && count && (value[count - 1] & 0x80)) {
switch (count) {
case 0:
return BigInt(0);

case 1:
return BigInt(((u8a[0] ^ 0x0000_00ff) * -1) - 1);
return BigInt(((value[0] ^ 0x0000_00ff) * -1) - 1);

case 2:
return BigInt((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
return BigInt((((value[0] + (value[1] << 8)) ^ 0x0000_ffff) * -1) - 1);

case 4:
return BigInt((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
return BigInt((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
}

const dvI = new DataView(u8a.buffer, u8a.byteOffset);
const dvI = new DataView(value.buffer, value.byteOffset);

if (count === 8) {
return dvI.getBigInt64(0, true);
Expand All @@ -52,7 +53,7 @@ export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = fals
}

if (mod) {
result = (result * U8_MAX) + BigInt(u8a[0] ^ 0xff);
result = (result * U8_MAX) + BigInt(value[0] ^ 0xff);
}

return (result * -_1n) - _1n;
Expand All @@ -63,16 +64,16 @@ export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = fals
return BigInt(0);

case 1:
return BigInt(u8a[0]);
return BigInt(value[0]);

case 2:
return BigInt(u8a[0] + (u8a[1] << 8));
return BigInt(value[0] + (value[1] << 8));

case 4:
return BigInt(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00));
return BigInt(value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00));
}

const dvI = new DataView(u8a.buffer, u8a.byteOffset);
const dvI = new DataView(value.buffer, value.byteOffset);

switch (count) {
case 8:
Expand All @@ -90,7 +91,7 @@ export function u8aToBigInt (value: Uint8Array, { isLe = true, isNegative = fals
}

if (mod) {
result = (result * U8_MAX) + BigInt(u8a[0]);
result = (result * U8_MAX) + BigInt(value[0]);
}

return result;
Expand Down
39 changes: 20 additions & 19 deletions packages/util/src/u8a/toBn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,43 @@ import { BN } from '../bn/bn.js';
export function u8aToBn (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): BN {
// slice + reverse is expensive, however SCALE is LE by default so this is the path
// we are most interested in (the BE is added for the sake of being comprehensive)
const u8a = isLe
? value
: value.slice().reverse();
const count = u8a.length;
if (!isLe) {
value = value.slice().reverse();
}

const count = value.length;

// shortcut for <= u48 values - in this case the manual conversion
// here seems to be more efficient than passing the full array
if (isNegative && count && (u8a[count - 1] & 0x80)) {
if (isNegative && count && (value[count - 1] & 0x80)) {
// Most common case i{8, 16, 32} default LE SCALE-encoded
// For <= 32, we also optimize the xor to a single op
switch (count) {
case 0:
return new BN(0);

case 1:
return new BN(((u8a[0] ^ 0x0000_00ff) * -1) - 1);
return new BN(((value[0] ^ 0x0000_00ff) * -1) - 1);

case 2:
return new BN((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
return new BN((((value[0] + (value[1] << 8)) ^ 0x0000_ffff) * -1) - 1);

case 3:
return new BN((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);
return new BN((((value[0] + (value[1] << 8) + (value[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);

case 4:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return new BN((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
return new BN((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);

case 5:
return new BN(((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((u8a[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);
return new BN(((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((value[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);

case 6:
return new BN(((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((u8a[4] + (u8a[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);
return new BN(((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((value[4] + (value[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);

default:
return new BN(u8a, 'le').fromTwos(count * 8);
return new BN(value, 'le').fromTwos(count * 8);
}
}

Expand All @@ -75,26 +76,26 @@ export function u8aToBn (value: Uint8Array, { isLe = true, isNegative = false }:
return new BN(0);

case 1:
return new BN(u8a[0]);
return new BN(value[0]);

case 2:
return new BN(u8a[0] + (u8a[1] << 8));
return new BN(value[0] + (value[1] << 8));

case 3:
return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16));
return new BN(value[0] + (value[1] << 8) + (value[2] << 16));

case 4:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00));
return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00));

case 5:
return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8)) * 0x1_00_00_00));
return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8)) * 0x1_00_00_00));

case 6:
return new BN(u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8) + (u8a[5] << 16)) * 0x1_00_00_00));
return new BN(value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8) + (value[5] << 16)) * 0x1_00_00_00));

default:
return new BN(u8a, 'le');
return new BN(value, 'le');
}
}
35 changes: 18 additions & 17 deletions packages/util/src/u8a/toNumber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,40 @@ import type { ToBnOptions } from '../types.js';
export function u8aToNumber (value: Uint8Array, { isLe = true, isNegative = false }: ToBnOptions = {}): number {
// slice + reverse is expensive, however SCALE is LE by default so this is the path
// we are most interested in (the BE is added for the sake of being comprehensive)
const u8a = isLe
? value
: value.slice().reverse();
const count = u8a.length;
if (!isLe) {
value = value.slice().reverse();
}

const count = value.length;

// When the value is a i{8, 16, 24, 32, 40, 40} values and the top-most bit
// indicates a signed value, we use a two's complement conversion. If one of these
// flags are not set, we just do a normal unsigned conversion (the same shortcut
// applies in both the u8aTo{BigInt, Bn} conversions as well)
if (isNegative && count && (u8a[count - 1] & 0x80)) {
if (isNegative && count && (value[count - 1] & 0x80)) {
switch (count) {
case 0:
return 0;

case 1:
return (((u8a[0] ^ 0x0000_00ff) * -1) - 1);
return (((value[0] ^ 0x0000_00ff) * -1) - 1);

case 2:
return ((((u8a[0] + (u8a[1] << 8)) ^ 0x0000_ffff) * -1) - 1);
return ((((value[0] + (value[1] << 8)) ^ 0x0000_ffff) * -1) - 1);

case 3:
return ((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);
return ((((value[0] + (value[1] << 8) + (value[2] << 16)) ^ 0x00ff_ffff) * -1) - 1);

case 4:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return ((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);
return ((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) * -1) - 1);

case 5:
return (((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((u8a[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);
return (((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + ((value[4] ^ 0xff) * 0x1_00_00_00_00)) * -1) - 1);

case 6:
return (((((u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((u8a[4] + (u8a[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);
return (((((value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00)) ^ 0xffff_ffff) + (((value[4] + (value[5] << 8)) ^ 0x0000_ffff) * 0x1_00_00_00_00)) * -1) - 1);

default:
throw new Error('Value more than 48-bits cannot be reliably converted');
Expand All @@ -54,24 +55,24 @@ export function u8aToNumber (value: Uint8Array, { isLe = true, isNegative = fals
return 0;

case 1:
return u8a[0];
return value[0];

case 2:
return u8a[0] + (u8a[1] << 8);
return value[0] + (value[1] << 8);

case 3:
return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16);
return value[0] + (value[1] << 8) + (value[2] << 16);

case 4:
// for the 3rd byte, we don't << 24 - since JS converts all bitwise operators to
// 32-bit, in the case where the top-most bit is set this yields a negative value
return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + (u8a[3] * 0x1_00_00_00);
return value[0] + (value[1] << 8) + (value[2] << 16) + (value[3] * 0x1_00_00_00);

case 5:
return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8)) * 0x1_00_00_00);
return value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8)) * 0x1_00_00_00);

case 6:
return u8a[0] + (u8a[1] << 8) + (u8a[2] << 16) + ((u8a[3] + (u8a[4] << 8) + (u8a[5] << 16)) * 0x1_00_00_00);
return value[0] + (value[1] << 8) + (value[2] << 16) + ((value[3] + (value[4] << 8) + (value[5] << 16)) * 0x1_00_00_00);

default:
throw new Error('Value more than 48-bits cannot be reliably converted');
Expand Down

0 comments on commit cf9082e

Please sign in to comment.