Skip to content

Commit

Permalink
fix!: Remove signed option for intToBigInt
Browse files Browse the repository at this point in the history
  • Loading branch information
janniks committed Sep 17, 2024
1 parent 3336fe2 commit b889fcd
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 86 deletions.
7 changes: 7 additions & 0 deletions .github/MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [CLI](#cli)
- [Triplesec](#triplesec)
- [Advanced: WireType](#advanced-wiretype)
- [Advanced: Signed BigInt](#advanced-signed-bigint)
- [Stacks.js (\<=4.x.x) → (5.x.x)](#stacksjs-4xx--5xx)
- [Breaking Changes](#breaking-changes-1)
- [Buffer to Uint8Array](#buffer-to-uint8array)
Expand Down Expand Up @@ -41,6 +42,7 @@
- Remove legacy CLI methods. [Read more...](#cli)
- Disable legacy `triplesec` mnemonic encryption support. [Read more...](#triplesec)
- **Advanced:** Rename `MessageType` and related concepts to `WireType`. [Read more...](#advanced-wiretype)
- **Advanced:** Removes two's complement compatibilty from `intToBigInt` parser method. [Read more...](#advanced-signed-bigint)

### Stacks Network

Expand Down Expand Up @@ -299,6 +301,11 @@ More types were renamed to indicate use for serialization to _wire-format_:
- `StandardPrincipal``StandardPrincipalWire`
- `ContractPrincipal``ContractPrincipalWire`

### Advanced: Signed BigInt

The `intToBigInt` method no longer supports two's complement signed integers and removed the `signed` boolean parameter.
This likely was a misunderstood and unused feature.

## Stacks.js (&lt;=4.x.x) → (5.x.x)

### Breaking Changes
Expand Down
6 changes: 3 additions & 3 deletions packages/bns/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export async function buildPreorderNamespaceTx({
type: 'stx-postcondition',
address: publicKeyToAddress(network.addressVersion.singleSig, publicKey),
condition: 'eq',
amount: intToBigInt(stxToBurn, true),
amount: intToBigInt(stxToBurn),
};

return makeBnsContractCall({
Expand Down Expand Up @@ -515,7 +515,7 @@ export async function buildPreorderNameTx({
type: 'stx-postcondition',
address: publicKeyToAddress(network.addressVersion.singleSig, publicKey),
condition: 'eq',
amount: intToBigInt(stxToBurn, true),
amount: intToBigInt(stxToBurn),
};

return makeBnsContractCall({
Expand Down Expand Up @@ -807,7 +807,7 @@ export async function buildRenewNameTx({
type: 'stx-postcondition',
address: publicKeyToAddress(network.addressVersion.singleSig, publicKey),
condition: 'eq',
amount: intToBigInt(stxToBurn, true),
amount: intToBigInt(stxToBurn),
};

return makeBnsContractCall({
Expand Down
81 changes: 31 additions & 50 deletions packages/common/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,64 +317,40 @@ export function getGlobalObjects<K extends Extract<keyof Window, string>>(

export type IntegerType = number | string | bigint | Uint8Array;

export function intToBytes(value: IntegerType, signed: boolean, byteLength: number): Uint8Array {
return bigIntToBytes(intToBigInt(value, signed), byteLength);
export function intToBytes(value: IntegerType, byteLength: number): Uint8Array {
return bigIntToBytes(intToBigInt(value), byteLength);
}

//todo: add default param to `signed` (should only be needed in rare use-cases, not typical users) `next`
export function intToBigInt(value: IntegerType, signed: boolean): bigint {
let parsedValue = value;

if (typeof parsedValue === 'number') {
if (!Number.isInteger(parsedValue)) {
/**
* Converts an integer-compatible value to a bigint
* @param value - The value to convert to a bigint
* @returns The bigint representation of the value
*
* @example
* ```ts
* intToBigInt(123); // 123n
* intToBigInt('0xbeef'); // 48879n
* ```
*/
export function intToBigInt(value: IntegerType): bigint {
if (typeof value === 'bigint') return value;
if (typeof value === 'string') return BigInt(value);
if (typeof value === 'number') {
if (!Number.isInteger(value)) {
throw new RangeError(`Invalid value. Values of type 'number' must be an integer.`);
}
if (parsedValue > Number.MAX_SAFE_INTEGER) {
if (value > Number.MAX_SAFE_INTEGER) {
throw new RangeError(
`Invalid value. Values of type 'number' must be less than or equal to ${Number.MAX_SAFE_INTEGER}. For larger values, try using a BigInt instead.`
);
}
return BigInt(parsedValue);
}
if (typeof parsedValue === 'string') {
// If hex string then convert to bytes then fall through to the bytes condition
if (parsedValue.toLowerCase().startsWith('0x')) {
// Trim '0x' hex-prefix
let hex = parsedValue.slice(2);

// Allow odd-length strings like `0xf` -- some libs output these, or even just `0x${num.toString(16)}`
hex = hex.padStart(hex.length + (hex.length % 2), '0');

parsedValue = hexToBytes(hex);
} else {
try {
return BigInt(parsedValue);
} catch (error) {
if (error instanceof SyntaxError) {
throw new RangeError(`Invalid value. String integer '${parsedValue}' is not finite.`);
}
}
}
}
if (typeof parsedValue === 'bigint') {
return parsedValue;
}
if (parsedValue instanceof Uint8Array) {
if (signed) {
// Allow byte arrays smaller than 128-bits to be passed.
// This allows positive signed ints like `0x08` (8) or negative signed
// ints like `0xf8` (-8) to be passed without having to pad to 16 bytes.
const bn = fromTwos(
BigInt(`0x${bytesToHex(parsedValue)}`),
BigInt(parsedValue.byteLength * 8)
);
return BigInt(bn.toString());
} else {
return BigInt(`0x${bytesToHex(parsedValue)}`);
}
return BigInt(value);
}

if (isInstance(value, Uint8Array)) return BigInt(`0x${bytesToHex(value)}`);

throw new TypeError(
`Invalid value type. Must be a number, bigint, integer-string, hex-string, or Uint8Array.`
`intToBigInt: Invalid value type. Must be a number, bigint, BigInt-compatible string, or Uint8Array.`
);
}

Expand Down Expand Up @@ -414,7 +390,7 @@ export function hexToBigInt(hex: string): bigint {
* @ignore
*/
export function intToHex(integer: IntegerType, lengthBytes = 8): string {
const value = typeof integer === 'bigint' ? integer : intToBigInt(integer, false);
const value = typeof integer === 'bigint' ? integer : intToBigInt(integer);
return value.toString(16).padStart(lengthBytes * 2, '0');
}

Expand Down Expand Up @@ -463,9 +439,14 @@ function nthBit(value: bigint, n: bigint) {
return value & (BigInt(1) << n);
}

/** @internal */
export function bytesToTwosBigInt(bytes: Uint8Array): bigint {
return fromTwos(BigInt(`0x${bytesToHex(bytes)}`), BigInt(bytes.byteLength * 8));
}

/**
* Converts from two's complement to signed number
* @ignore
* @internal
*/
export function fromTwos(value: bigint, width: bigint) {
if (nthBit(value, width - BigInt(1))) {
Expand Down
4 changes: 1 addition & 3 deletions packages/stacking/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1544,9 +1544,7 @@ export class StackingClient {
*/
modifyLockTxFee({ tx, amountMicroStx }: { tx: StacksTransaction; amountMicroStx: IntegerType }) {
const fee = getFee(tx.auth);
(tx.payload as ContractCallPayload).functionArgs[0] = uintCV(
intToBigInt(amountMicroStx, false) - fee
);
(tx.payload as ContractCallPayload).functionArgs[0] = uintCV(intToBigInt(amountMicroStx) - fee);
return tx;
}

Expand Down
32 changes: 16 additions & 16 deletions packages/transactions/src/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ export function createSingleSigSpendingCondition(
return {
hashMode,
signer,
nonce: intToBigInt(nonce, false),
fee: intToBigInt(fee, false),
nonce: intToBigInt(nonce),
fee: intToBigInt(fee),
keyEncoding,
signature: emptyMessageSignature(),
};
Expand All @@ -162,8 +162,8 @@ export function createMultiSigSpendingCondition(
return {
hashMode,
signer,
nonce: intToBigInt(nonce, false),
fee: intToBigInt(fee, false),
nonce: intToBigInt(nonce),
fee: intToBigInt(fee),
fields: [],
signaturesRequired: numSigs,
};
Expand Down Expand Up @@ -213,8 +213,8 @@ export function serializeSingleSigSpendingCondition(
const bytesArray = [
condition.hashMode,
hexToBytes(condition.signer),
intToBytes(condition.nonce, false, 8),
intToBytes(condition.fee, false, 8),
intToBytes(condition.nonce, 8),
intToBytes(condition.fee, 8),
condition.keyEncoding as number,
serializeMessageSignatureBytes(condition.signature),
];
Expand All @@ -227,8 +227,8 @@ export function serializeMultiSigSpendingCondition(
const bytesArray = [
condition.hashMode,
hexToBytes(condition.signer),
intToBytes(condition.nonce, false, 8),
intToBytes(condition.fee, false, 8),
intToBytes(condition.nonce, 8),
intToBytes(condition.fee, 8),
];

const fields = createLPList(condition.fields);
Expand Down Expand Up @@ -356,8 +356,8 @@ export function makeSigHashPreSign(
const sigHash =
curSigHash +
bytesToHex(new Uint8Array([authType])) +
bytesToHex(intToBytes(fee, false, 8)) +
bytesToHex(intToBytes(nonce, false, 8));
bytesToHex(intToBytes(fee, 8)) +
bytesToHex(intToBytes(nonce, 8));

if (hexToBytes(sigHash).byteLength !== hashLength) {
throw Error('Invalid signature hash length');
Expand Down Expand Up @@ -620,13 +620,13 @@ export function setFee(auth: Authorization, amount: IntegerType): Authorization
case AuthType.Standard:
const spendingCondition = {
...auth.spendingCondition,
fee: intToBigInt(amount, false),
fee: intToBigInt(amount),
};
return { ...auth, spendingCondition };
case AuthType.Sponsored:
const sponsorSpendingCondition = {
...auth.sponsorSpendingCondition,
fee: intToBigInt(amount, false),
fee: intToBigInt(amount),
};
return { ...auth, sponsorSpendingCondition };
}
Expand All @@ -644,7 +644,7 @@ export function getFee(auth: Authorization): bigint {
export function setNonce(auth: Authorization, nonce: IntegerType): Authorization {
const spendingCondition = {
...auth.spendingCondition,
nonce: intToBigInt(nonce, false),
nonce: intToBigInt(nonce),
};

return {
Expand All @@ -656,7 +656,7 @@ export function setNonce(auth: Authorization, nonce: IntegerType): Authorization
export function setSponsorNonce(auth: SponsoredAuthorization, nonce: IntegerType): Authorization {
const sponsorSpendingCondition = {
...auth.sponsorSpendingCondition,
nonce: intToBigInt(nonce, false),
nonce: intToBigInt(nonce),
};

return {
Expand All @@ -671,8 +671,8 @@ export function setSponsor(
): Authorization {
const sc = {
...sponsorSpendingCondition,
nonce: intToBigInt(sponsorSpendingCondition.nonce, false),
fee: intToBigInt(sponsorSpendingCondition.fee, false),
nonce: intToBigInt(sponsorSpendingCondition.nonce),
fee: intToBigInt(sponsorSpendingCondition.fee),
};

return {
Expand Down
4 changes: 2 additions & 2 deletions packages/transactions/src/clarity/deserialize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { bytesToAscii, bytesToUtf8, hexToBytes } from '@stacks/common';
import { bytesToAscii, bytesToTwosBigInt, bytesToUtf8, hexToBytes } from '@stacks/common';
import {
ClarityValue,
ClarityWireType,
Expand Down Expand Up @@ -59,7 +59,7 @@ export function deserializeCV<T extends ClarityValue = ClarityValue>(

switch (type) {
case ClarityWireType.int:
return intCV(bytesReader.readBytes(16)) as T;
return intCV(bytesToTwosBigInt(bytesReader.readBytes(16))) as T;

case ClarityWireType.uint:
return uintCV(bytesReader.readBytes(16)) as T;
Expand Down
20 changes: 17 additions & 3 deletions packages/transactions/src/clarity/values/intCV.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { IntegerType, intToBigInt } from '@stacks/common';
import {
IntegerType,
bytesToTwosBigInt,
hexToBytes,
intToBigInt,
isInstance,
} from '@stacks/common';
import { ClarityType } from '../constants';
import { IntCV, UIntCV } from '../types';

Expand Down Expand Up @@ -27,7 +33,15 @@ const MIN_I128 = BigInt('-170141183460469231731687303715884105728'); // (-2 ** 1
* {@link https://github.com/hirosystems/stacks.js/blob/main/packages/transactions/tests/clarity.test.ts | clarity test cases for more examples}
*/
export const intCV = (value: IntegerType): IntCV => {
const bigInt = intToBigInt(value, true);
// ensure compatibility with twos-complement encoded hex-strings
if (typeof value === 'string' && value.toLowerCase().startsWith('0x')) {
value = bytesToTwosBigInt(hexToBytes(value));
}

// ensure compatibility with twos-complement encoded byte arrays
if (isInstance(value, Uint8Array)) value = bytesToTwosBigInt(value);

const bigInt = intToBigInt(value);
if (bigInt > MAX_I128) {
throw new RangeError(`Cannot construct clarity integer from value greater than ${MAX_I128}`);
} else if (bigInt < MIN_I128) {
Expand Down Expand Up @@ -55,7 +69,7 @@ export const intCV = (value: IntegerType): IntCV => {
* {@link https://github.com/hirosystems/stacks.js/blob/main/packages/transactions/tests/clarity.test.ts | clarity test cases for more examples}
*/
export const uintCV = (value: IntegerType): UIntCV => {
const bigInt = intToBigInt(value, false);
const bigInt = intToBigInt(value);
if (bigInt < MIN_U128) {
throw new RangeError('Cannot construct unsigned clarity integer from negative value');
} else if (bigInt > MAX_U128) {
Expand Down
4 changes: 2 additions & 2 deletions packages/transactions/src/pc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ class PartialPcFtWithCode {
type: 'stx-postcondition',
address: this.address,
condition: this.code,
amount: intToBigInt(this.amount, true).toString(),
amount: intToBigInt(this.amount).toString(),
};
}

Expand All @@ -206,7 +206,7 @@ class PartialPcFtWithCode {
type: 'ft-postcondition',
address: this.address,
condition: this.code,
amount: intToBigInt(this.amount, true).toString(),
amount: intToBigInt(this.amount).toString(),
asset: `${contractId}::${tokenName}`,
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/transactions/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class StacksTransaction {
if ('amount' in payload) {
this.payload = {
...payload,
amount: intToBigInt(payload.amount, false),
amount: intToBigInt(payload.amount),
};
} else {
this.payload = payload;
Expand Down
2 changes: 1 addition & 1 deletion packages/transactions/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export { verify as verifySignature } from '@noble/secp256k1';
export const randomBytes = (bytesLength?: number): Uint8Array => utils.randomBytes(bytesLength);

export const leftPadHex = (hexString: string): string =>
hexString.length % 2 == 0 ? hexString : `0${hexString}`;
hexString.length % 2 ? `0${hexString}` : hexString;

export const leftPadHexToLength = (hexString: string, length: number): string =>
hexString.padStart(length, '0');
Expand Down
2 changes: 1 addition & 1 deletion packages/transactions/src/wire/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function createTokenTransferPayload(
type: StacksWireType.Payload,
payloadType: PayloadType.TokenTransfer,
recipient,
amount: intToBigInt(amount, false),
amount: intToBigInt(amount),
memo: memo ?? createMemoString(''),
};
}
Expand Down
Loading

0 comments on commit b889fcd

Please sign in to comment.