From 028f3f0c237cb1ef8cc3bb727edcbbafd1f5c01f Mon Sep 17 00:00:00 2001 From: Angel Castillo Date: Fri, 27 Sep 2024 08:19:06 +0800 Subject: [PATCH] fix(core): plutus integers are now decoded correctly from indefinite length array form --- .../core/src/Serialization/CBOR/CborReader.ts | 2 +- .../Serialization/PlutusData/PlutusData.ts | 42 +++++++++++++++++-- .../Serialization/CBOR/CborReader.test.ts | 14 +++++++ .../test/Serialization/PlutusData.test.ts | 42 +++++++++++++++++++ 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/packages/core/src/Serialization/CBOR/CborReader.ts b/packages/core/src/Serialization/CBOR/CborReader.ts index 00bbf858c10..bdb4d8a2996 100644 --- a/packages/core/src/Serialization/CBOR/CborReader.ts +++ b/packages/core/src/Serialization/CBOR/CborReader.ts @@ -886,7 +886,7 @@ export class CborReader { const { length: chunkLength, bytesRead } = CborReader.#peekDefiniteLength(nextInitialByte, data.slice(i)); const payloadSize = bytesRead + Number(chunkLength); - concat = Buffer.concat([concat, this.#data.slice(i + (payloadSize - chunkLength), i + payloadSize)]); + concat = Buffer.concat([concat, data.slice(i + (payloadSize - chunkLength), i + payloadSize)]); i += payloadSize; diff --git a/packages/core/src/Serialization/PlutusData/PlutusData.ts b/packages/core/src/Serialization/PlutusData/PlutusData.ts index 7f28e5c9bbc..97ef6e204a5 100644 --- a/packages/core/src/Serialization/PlutusData/PlutusData.ts +++ b/packages/core/src/Serialization/PlutusData/PlutusData.ts @@ -14,6 +14,7 @@ const MAX_WORD64 = 18_446_744_073_709_551_615n; const INDEFINITE_BYTE_STRING = new Uint8Array([95]); const MAX_BYTE_STRING_CHUNK_SIZE = 64; const HASH_LENGTH_IN_BYTES = 32; +const MINUS_ONE = BigInt(-1); /** * A type corresponding to the Plutus Core Data datatype. @@ -37,7 +38,7 @@ export class PlutusData { * * @returns The CBOR representation of this instance as a Uint8Array. */ - // eslint-disable-next-line complexity + // eslint-disable-next-line complexity, sonarjs/cognitive-complexity, max-statements toCbor(): HexBlob { if (this.#originalBytes) return this.#originalBytes; @@ -92,9 +93,22 @@ export class PlutusData { ) { writer.writeInt(this.#integer!); } else { - // Otherwise, it would be encoded as a bignum anyway, so we manually do the bignum - // encoding with a bytestring inside. - writer.writeBigInteger(this.#integer!); + const serializedBigint = PlutusData.bigintToBuffer(this.#integer!); + + writer.writeTag(this.#integer! < 0 ? CborTag.NegativeBigNum : CborTag.UnsignedBigNum); + + if (serializedBigint.length <= MAX_BYTE_STRING_CHUNK_SIZE) { + writer.writeByteString(serializedBigint); + } else { + writer.writeEncodedValue(INDEFINITE_BYTE_STRING); + + for (let i = 0; i < serializedBigint.length; i += MAX_BYTE_STRING_CHUNK_SIZE) { + const chunk = serializedBigint.slice(i, i + MAX_BYTE_STRING_CHUNK_SIZE); + writer.writeByteString(chunk); + } + + writer.writeEndArray(); + } } cbor = bytesToHex(writer.encode()); @@ -468,4 +482,24 @@ export class PlutusData { } return ret; } + + /** + * Converts a bigint to an Uint8Array. + * + * @param value The bigint to serialize. + * @returns The Uint8Array with the bigint serialized data. + */ + private static bigintToBuffer(value: bigint): Uint8Array { + if (value < 0) { + value = -value + MINUS_ONE; + } + + let str = value.toString(16); + + if (str.length % 2) { + str = `0${str}`; + } + + return Buffer.from(str, 'hex'); + } } diff --git a/packages/core/test/Serialization/CBOR/CborReader.test.ts b/packages/core/test/Serialization/CBOR/CborReader.test.ts index cc3788a926b..f2a22c01ad9 100644 --- a/packages/core/test/Serialization/CBOR/CborReader.test.ts +++ b/packages/core/test/Serialization/CBOR/CborReader.test.ts @@ -374,6 +374,20 @@ describe('CborReader', () => { '64676273786767746f6768646a7074657476746b636f6376796669647171676775726a687268716169697370717275656c687679707178656577707279667677' ); expect(reader.peekState()).toBe(CborReaderState.Finished); + + reader = new CborReader( + HexBlob( + '5f584037d34fac60a7dd2edba0c76fa58862c91c45ff4298e9134ba8e76be9a7513d88865bfdb9315073dc2690b0f2b59a232fbfa0a8a504df6ee9bb78e3f33fbdfef95529c9e74ff30ffe1bd1cc5795c37535899dba800000ff' + ) + ); + + expect(reader.peekState()).toBe(CborReaderState.StartIndefiniteLengthByteString); + array = reader.readByteString(); + expect(array.length).toEqual(85); + expect(Buffer.from(array).toString('hex')).toEqual( + '37d34fac60a7dd2edba0c76fa58862c91c45ff4298e9134ba8e76be9a7513d88865bfdb9315073dc2690b0f2b59a232fbfa0a8a504df6ee9bb78e3f33fbdfef929c9e74ff30ffe1bd1cc5795c37535899dba800000' + ); + expect(reader.peekState()).toBe(CborReaderState.Finished); }); }); diff --git a/packages/core/test/Serialization/PlutusData.test.ts b/packages/core/test/Serialization/PlutusData.test.ts index dd9e7648e0d..7b0c6f3e45b 100644 --- a/packages/core/test/Serialization/PlutusData.test.ts +++ b/packages/core/test/Serialization/PlutusData.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-len */ import { Cardano, Serialization } from '../../src'; import { HexBlob } from '@cardano-sdk/util'; @@ -66,6 +67,18 @@ describe('PlutusData', () => { const data = Serialization.PlutusData.fromCbor(HexBlob('3bffffffffffffffff')); expect(data.asInteger()).toEqual(-18_446_744_073_709_551_616n); }); + + it('can decode a positive tagged indefinite length unbounded int', () => { + const data = Serialization.PlutusData.fromCbor( + HexBlob( + 'c25f584037d34fac60a7dd2edba0c76fa58862c91c45ff4298e9134ba8e76be9a7513d88865bfdb9315073dc2690b0f2b59a232fbfa0a8a504df6ee9bb78e3f33fbdfef95529c9e74ff30ffe1bd1cc5795c37535899dba800000ff' + ) + ); + expect(data.asInteger()).toEqual( + // eslint-disable-next-line max-len + 1_093_929_156_918_367_016_766_069_563_027_239_416_446_778_893_307_251_997_971_794_948_729_105_062_347_369_330_146_869_223_033_199_554_831_433_128_491_376_164_494_134_119_896_793_625_745_623_928_731_109_781_036_903_510_617_119_765_359_815_723_399_113_165_600_284_443_934_720n + ); + }); }); describe('Bytes', () => { @@ -130,6 +143,35 @@ describe('PlutusData', () => { ); expect([...data.asBoundedBytes()!]).toEqual(payload); }); + + it('can decode/encode a list of big integers', () => { + // Arrange + + const expectedPlutusData: Cardano.PlutusList = { + items: [ + 1_093_929_156_918_367_016_766_069_563_027_239_416_446_778_893_307_251_997_971_794_948_729_105_062_347_369_330_146_869_223_033_199_554_831_433_128_491_376_164_494_134_119_896_793_625_745_623_928_731_109_781_036_903_510_617_119_765_359_815_723_399_113_165_600_284_443_934_720n, + 2_768_491_094_397_106_413_284_351_268_798_781_278_061_973_163_918_667_373_508_176_781_108_678_876_832_888_565_950_388_553_255_499_815_619_207_549_146_245_084_281_150_783_450_096_035_638_439_655_721_496_227_482_399_093_555_200_000_000_000_000_000_000_000_000_000_000_000_000n, + 2_768_491_094_397_106_413_284_351_268_798_781_278_061_973_163_918_667_373_508_176_781_108_678_876_832_888_565_950_388_553_255_499_815_619_207_549_146_245_084_281_150_783_450_096_035_638_439_655_721_496_227_482_399_093_555_200_000_000_000_000_000_000_000_000_000_000_000_000n, + 1_127_320_948_699_467_529_606_464_548_687_160_198_167_487_105_208_190_997_153_720_362_564_942_186_550_892_230_582_242_980_573_812_448_057_150_419_530_802_096_156_402_677_128_058_112_319_272_573_039_196_273_296_535_693_983_366_369_964_092_325_725_072_645_646_768_416_006_720n, + 678_966_618_629_088_994_577_385_052_394_593_905_048_788_216_453_653_741_455_475_012_343_328_029_630_393_478_083_358_655_655_534_689_789_017_294_468_365_725_065_895_808_744_013_442_165_812_351_180_871_208_842_081_615_673_249_725_577_503_335_455_257_844_242_272_891_195_840n, + 1_337_829_155_615_373_710_780_861_189_358_723_839_738_261_900_670_472_008_493_768_766_460_943_065_914_931_970_040_774_692_071_540_815_257_661_221_428_415_268_570_880_739_215_388_841_910_028_989_315_213_224_986_535_176_632_464_067_341_466_233_795_236_134_699_058_357_952_960n, + 45_981_213_582_240_091_300_385_870_382_262_347_274_104_141_060_516_509_284_758_089_043_905_194_449_918_733_499_912_740_694_341_485_053_723_341_097_850_038_365_519_925_374_324_306_213_051_881_991_025_304_309_829_953_615_052_414_155_047_559_800_693_983_587_151_987_253_760n, + 2_413_605_787_847_473_064_058_493_109_882_761_763_812_632_923_885_676_112_901_376_523_745_345_875_592_342_323_079_462_001_682_936_368_998_782_686_824_629_943_810_471_167_748_859_099_323_567_551_094_056_876_663_897_197_968_204_837_564_889_906_128_763_937_156_053n + ] + }; + + const expectedCbor = HexBlob( + '9fc25f584037d34fac60a7dd2edba0c76fa58862c91c45ff4298e9134ba8e76be9a7513d88865bfdb9315073dc2690b0f2b59a232fbfa0a8a504df6ee9bb78e3f33fbdfef95529c9e74ff30ffe1bd1cc5795c37535899dba800000ffc25f58408d4820519e9bba2d6556c87b100709082f4c8958769899eb5d288b6f9ea9e0723df7211959860edea5829c9732422d25962e3945c68a6089f50a18b0114248b7555feea4851e9f099180600000000000000000000000ffc25f58408d4820519e9bba2d6556c87b100709082f4c8958769899eb5d288b6f9ea9e0723df7211959860edea5829c9732422d25962e3945c68a6089f50a18b0114248b7555feea4851e9f099180600000000000000000000000ffc25f584039878c5f4d4063e9a2ee75a3fbdd1492c3cad46f4ecbae977ac94b709a730e367edf9dae05acd59638d1dec25e2351c2eecb871694afae979de7085b522efe1355634138bbd920200d574cdf400324cdd1aafe10a240ffc25f584022a6282a7d960570c4c729decd677ec617061f0e501249c41f8724c89dc97dc0d24917bdb7a7ebd7c079c1c56fa21af0f119168966356ea384fb711cb766015e55bfc5bc86583f6a82ae605a93e7bf974ae74cd051c0ffc25f58404445ab8649611ee8f74a3c31e504a2f25f2f7631ef6ef828a405542904d84c997304b1b332d528ee54873b03cfb73cd3c5b35b91184f6846afccec7271bda8a05563ba46aed8c82611da47fd608d027447f8391161c0ffc25f58400258b535c4d4a22a483b22b2f5c5c65bed9e7de59266f6bbaa8997edf5bec6bb5d203641bb58d8ade1a3a5b4e5f923df502cf1e47691865fe1984eacef3be96a551ed585e070265db203a8866726bed053cb6c8aa200ffc25f5840021104310667ec434e9e2cd9fa71853593c42e1b55865ac49f80b2ea22beeec9b4a55e9545055a2bcde3a78d36836df11df0f91c1dae9a8aee58419b8650bc6c529361f9601a4005051b045d05f39a5f00ebd5ffff' + ); + + // Act + const actualPlutusData = Serialization.PlutusData.fromCbor(expectedCbor); + const actualCbor = Serialization.PlutusData.fromCore(expectedPlutusData).toCbor(); + + // Assert + expect((actualPlutusData.toCore() as Cardano.PlutusList).items).toEqual(expectedPlutusData.items); + expect(actualCbor).toEqual(expectedCbor); + }); }); describe('List', () => {