From d3dec5bd2507da89be78f6dd02bf148f2a453940 Mon Sep 17 00:00:00 2001 From: Pavol Rusnak Date: Sat, 17 Aug 2024 23:30:49 +0200 Subject: [PATCH 1/5] add token v4 to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8c413796..1e1e6ca9 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Supported token formats: - [x] v1 read - [x] v2 read (deprecated) - [x] v3 read/write +- [x] v4 read/write ## Usage From 404730997d955b58c4c7bde26377322e8742c983 Mon Sep 17 00:00:00 2001 From: Alex Lewin Date: Fri, 23 Aug 2024 13:13:33 -0400 Subject: [PATCH 2/5] feat: imporved typesafety for decode cbor functions --- src/cbor.ts | 80 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/src/cbor.ts b/src/cbor.ts index 0436ff74..07b8a7be 100644 --- a/src/cbor.ts +++ b/src/cbor.ts @@ -1,3 +1,26 @@ +type SimpleValue = boolean | null | undefined; + +export type ResultObject = { [key: string]: ResultValue }; +export type ResultValue = + | SimpleValue + | number + | string + | Uint8Array + | Array + | ResultObject; + +type ResultKeyType = Extract; +export type ValidDecodedType = Extract; + +function isResultKeyType(value: ResultValue): value is ResultKeyType { + return typeof value === 'number' || typeof value === 'string'; +} + +type DecodeResult = { + value: T; + offset: number; +}; + export function encodeCBOR(value: any) { const buffer: Array = []; encodeItem(value, buffer); @@ -119,18 +142,14 @@ function encodeObject(value: { [key: string]: any }, buffer: Array) { encodeItem(value[key], buffer); } } -type DecodeResult = { - value: any; - offset: number; -}; -export function decodeCBOR(data: Uint8Array): any { +export function decodeCBOR(data: Uint8Array): ResultValue { const view = new DataView(data.buffer, data.byteOffset, data.byteLength); const result = decodeItem(view, 0); return result.value; } -function decodeItem(view: DataView, offset: number): DecodeResult { +function decodeItem(view: DataView, offset: number): DecodeResult { if (offset >= view.byteLength) { throw new Error('Unexpected end of data'); } @@ -158,7 +177,11 @@ function decodeItem(view: DataView, offset: number): DecodeResult { } } -function decodeLength(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeLength( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult { if (additionalInfo < 24) return { value: additionalInfo, offset }; if (additionalInfo === 24) return { value: view.getUint8(offset++), offset }; if (additionalInfo === 25) { @@ -180,17 +203,29 @@ function decodeLength(view: DataView, offset: number, additionalInfo: number): D throw new Error(`Unsupported length: ${additionalInfo}`); } -function decodeUnsigned(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeUnsigned( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult { const { value, offset: newOffset } = decodeLength(view, offset, additionalInfo); return { value, offset: newOffset }; } -function decodeSigned(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeSigned( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult { const { value, offset: newOffset } = decodeLength(view, offset, additionalInfo); return { value: -1 - value, offset: newOffset }; } -function decodeByteString(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeByteString( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult { const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); if (newOffset + length > view.byteLength) { throw new Error('Byte string length exceeds data length'); @@ -199,7 +234,11 @@ function decodeByteString(view: DataView, offset: number, additionalInfo: number return { value, offset: newOffset + length }; } -function decodeString(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeString( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult { const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); if (newOffset + length > view.byteLength) { throw new Error('String length exceeds data length'); @@ -209,7 +248,11 @@ function decodeString(view: DataView, offset: number, additionalInfo: number): D return { value, offset: newOffset + length }; } -function decodeArray(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeArray( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult> { const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); const array = []; let currentOffset = newOffset; @@ -221,12 +264,19 @@ function decodeArray(view: DataView, offset: number, additionalInfo: number): De return { value: array, offset: currentOffset }; } -function decodeMap(view: DataView, offset: number, additionalInfo: number): DecodeResult { +function decodeMap( + view: DataView, + offset: number, + additionalInfo: number +): DecodeResult> { const { value: length, offset: newOffset } = decodeLength(view, offset, additionalInfo); - const map: { [key: string]: any } = {}; + const map: { [key: string]: ResultValue } = {}; let currentOffset = newOffset; for (let i = 0; i < length; i++) { const keyResult = decodeItem(view, currentOffset); + if (!isResultKeyType(keyResult.value)) { + throw new Error('Invalid key type'); + } const valueResult = decodeItem(view, keyResult.offset); map[keyResult.value] = valueResult.value; currentOffset = valueResult.offset; @@ -251,7 +301,7 @@ function decodeSimpleAndFloat( view: DataView, offset: number, additionalInfo: number -): DecodeResult { +): DecodeResult { if (additionalInfo < 24) { switch (additionalInfo) { case 20: From c7f463747583239616349d03aa97110ceb83d034 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:19:56 +0200 Subject: [PATCH 3/5] propagate errors (integration test not fixed yet) --- src/CashuWallet.ts | 74 ++++++++++++++++++++------------------------- test/wallet.test.ts | 4 +-- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 23a31fae..0442bb04 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -130,22 +130,18 @@ class CashuWallet { privkey?: string; } ): Promise> { - try { - if (typeof token === 'string') { - token = getDecodedToken(token); - } - const tokenEntries: Array = token.token; - const proofs = await this.receiveTokenEntry(tokenEntries[0], { - keysetId: options?.keysetId, - preference: options?.preference, - counter: options?.counter, - pubkey: options?.pubkey, - privkey: options?.privkey - }); - return proofs; - } catch (error) { - throw new Error('Error when receiving'); + if (typeof token === 'string') { + token = getDecodedToken(token); } + const tokenEntries: Array = token.token; + const proofs = await this.receiveTokenEntry(tokenEntries[0], { + keysetId: options?.keysetId, + preference: options?.preference, + counter: options?.counter, + pubkey: options?.pubkey, + privkey: options?.privkey + }); + return proofs; } /** @@ -168,33 +164,29 @@ class CashuWallet { } ): Promise> { const proofs: Array = []; - try { - const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); - let preference = options?.preference; - if (!preference) { - preference = getDefaultAmountPreference(amount); - } - const keys = await this.getKeys(options?.keysetId); - const { payload, blindedMessages } = this.createSwapPayload( - amount, - tokenEntry.proofs, - keys, - preference, - options?.counter, - options?.pubkey, - options?.privkey - ); - const { signatures } = await CashuMint.split(tokenEntry.mint, payload); - const newProofs = this.constructProofs( - signatures, - blindedMessages.rs, - blindedMessages.secrets, - keys - ); - proofs.push(...newProofs); - } catch (error) { - throw new Error('Error receiving token entry'); + const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0); + let preference = options?.preference; + if (!preference) { + preference = getDefaultAmountPreference(amount); } + const keys = await this.getKeys(options?.keysetId); + const { payload, blindedMessages } = this.createSwapPayload( + amount, + tokenEntry.proofs, + keys, + preference, + options?.counter, + options?.pubkey, + options?.privkey + ); + const { signatures } = await CashuMint.split(tokenEntry.mint, payload); + const newProofs = this.constructProofs( + signatures, + blindedMessages.rs, + blindedMessages.secrets, + keys + ); + proofs.push(...newProofs); return proofs; } diff --git a/test/wallet.test.ts b/test/wallet.test.ts index b8bd97d8..913c02c4 100644 --- a/test/wallet.test.ts +++ b/test/wallet.test.ts @@ -180,14 +180,14 @@ describe('receive', () => { nock(mintUrl).post('/v1/swap').reply(400, { detail: msg }); const wallet = new CashuWallet(mint, { unit }); const result = await wallet.receive(tokenInput).catch((e) => e); - expect(result).toEqual(new Error('Error when receiving')); + expect(result).toEqual(new Error('tokens already spent. Secret: asdasdasd')); }); test('test receive could not verify proofs', async () => { nock(mintUrl).post('/v1/swap').reply(400, { code: 0, error: 'could not verify proofs.' }); const wallet = new CashuWallet(mint, { unit }); const result = await wallet.receive(tokenInput).catch((e) => e); - expect(result).toEqual(new Error('Error when receiving')); + expect(result).toEqual(new Error('could not verify proofs.')); }); }); From a53a62df8f00410a40bc52801ccff37bf41ac217 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 30 Aug 2024 00:21:34 +0200 Subject: [PATCH 4/5] fix integration test --- test/integration.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index bb43f253..2d069392 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -203,7 +203,7 @@ describe('mint api', () => { const result = await wallet .receive(encoded, { privkey: bytesToHex(privKeyAlice) }) .catch((e) => e); - expect(result).toEqual(new Error('Error when receiving')); + expect(result).toEqual(new Error('no valid signature provided for input.')); const proofs = await wallet.receive(encoded, { privkey: bytesToHex(privKeyBob) }); From 4e4a5ad275537e80a529e30d80e952d337c68f92 Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 30 Aug 2024 08:34:32 +0200 Subject: [PATCH 5/5] bumped version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 627f6803..69d22b89 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cashu/cashu-ts", - "version": "1.1.0-2", + "version": "1.1.0-3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@cashu/cashu-ts", - "version": "1.1.0-2", + "version": "1.1.0-3", "license": "MIT", "dependencies": { "@cashu/crypto": "^0.2.7", diff --git a/package.json b/package.json index effb1818..5e33c1f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cashu/cashu-ts", - "version": "1.1.0-2", + "version": "1.1.0-3", "description": "cashu library for communicating with a cashu mint", "main": "dist/lib/es5/index.js", "module": "dist/lib/es6/index.js",