From a86508bb4982a98a3bfd29aeb2deb6704a9cc7d1 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 21 Dec 2024 21:54:21 +0100 Subject: [PATCH 01/15] sign mint quotes --- src/CashuWallet.ts | 13 +++++++---- src/crypto/nut-20.ts | 36 ++++++++++++++++++++++++++++++ src/model/types/index.ts | 2 ++ src/model/types/mint/responses.ts | 4 ++++ src/model/types/wallet/payloads.ts | 8 +++++++ 5 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 src/crypto/nut-20.ts diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index a8ed3eac..c8abc85a 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -53,6 +53,7 @@ import { stripDleq, sumProofs } from './utils.js'; +import { signMintQuote } from './crypto/nut-20.js'; /** * The default number of proofs per denomination to keep in a wallet. @@ -601,13 +602,15 @@ class CashuWallet { * Requests a mint quote form the mint. Response returns a Lightning payment request for the requested given amount and unit. * @param amount Amount requesting for mint. * @param description optional description for the mint quote + * @param pubkey optional public key to lock the quote to * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit */ - async createMintQuote(amount: number, description?: string) { + async createMintQuote(amount: number, description?: string, pubkey?: string) { const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount, - description: description + description: description, + pubkey: pubkey, }; return await this.mint.createMintQuote(mintQuotePayload); } @@ -633,7 +636,7 @@ class CashuWallet { quote: string, options?: MintProofOptions ): Promise> { - let { keysetId, proofsWeHave, outputAmounts, counter, pubkey } = options || {}; + let { keysetId, proofsWeHave, outputAmounts, counter, pubkey, quotePrivkey } = options || {}; const keyset = await this.getKeys(keysetId); if (!outputAmounts && proofsWeHave) { outputAmounts = { @@ -649,9 +652,11 @@ class CashuWallet { counter, pubkey ); + const mintQuoteSignature = quotePrivkey ? signMintQuote(quotePrivkey, quote, blindedMessages) : undefined; const mintPayload: MintPayload = { outputs: blindedMessages, - quote: quote + quote: quote, + signature: mintQuoteSignature, }; const { signatures } = await this.mint.mint(mintPayload); return this.constructProofs(signatures, blindingFactors, secrets, keyset); diff --git a/src/crypto/nut-20.ts b/src/crypto/nut-20.ts new file mode 100644 index 00000000..03d6270d --- /dev/null +++ b/src/crypto/nut-20.ts @@ -0,0 +1,36 @@ +import { schnorr } from '@noble/curves/secp256k1'; +import { SerializedBlindedMessage } from "../model/types"; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; +import { sha256 } from '@noble/hashes/sha256'; + +function constructMessage(quote: string, blindedMessages: SerializedBlindedMessage[]): Uint8Array { + let message = quote; + for (const blindedMessage of blindedMessages) { + message += blindedMessage.B_; + } + const msgbytes = new TextEncoder().encode(message); + return sha256(msgbytes); +} + +export function signMintQuote( + privkey: string, + quote: string, + blindedMessages: SerializedBlindedMessage[], +): string { + const message = constructMessage(quote, blindedMessages); + const privkeyBytes = hexToBytes(privkey); + const signature = schnorr.sign(message, privkeyBytes); + return bytesToHex(signature); +} + +export function verifyMintQuoteSignature( + pubkey: string, + quote: string, + blindedMessages: SerializedBlindedMessage[], + signature: string, +): boolean { + const sigbytes = hexToBytes(signature); + const pubkeyBytes = hexToBytes(pubkey); + const message = constructMessage(quote, blindedMessages); + return schnorr.verify(sigbytes, message, pubkeyBytes); +} \ No newline at end of file diff --git a/src/model/types/index.ts b/src/model/types/index.ts index eec70e1d..beb4b3d9 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -85,6 +85,7 @@ export type RestoreOptions = { * - `counter`: optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * - `proofsWeHave`: optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts * - `pubkey`: optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! + * - `privkey`: optional private key to unlock the mint quote. Generates a schnorr signature of the payload. */ export type MintProofOptions = { keysetId?: string; @@ -92,6 +93,7 @@ export type MintProofOptions = { proofsWeHave?: Array; counter?: number; pubkey?: string; + quotePrivkey?: string; }; /** diff --git a/src/model/types/mint/responses.ts b/src/model/types/mint/responses.ts index 06ee8806..a641dedb 100644 --- a/src/model/types/mint/responses.ts +++ b/src/model/types/mint/responses.ts @@ -178,6 +178,10 @@ export type MintQuoteResponse = { * Timestamp of when the quote expires */ expiry: number; + /** + * Public key the quote is locked to + */ + pubkey?: string; } & ApiError; /** diff --git a/src/model/types/wallet/payloads.ts b/src/model/types/wallet/payloads.ts index 2ab98720..c9134ef9 100644 --- a/src/model/types/wallet/payloads.ts +++ b/src/model/types/wallet/payloads.ts @@ -62,6 +62,10 @@ export type MintPayload = { * Outputs (blinded messages) to be signed by the mint. */ outputs: Array; + /** + * Public key the quote is locked to + */ + signature?: string; }; /** @@ -80,6 +84,10 @@ export type MintQuotePayload = { * Description for the invoice */ description?: string; + /** + * Public key to lock the quote to + */ + pubkey?: string; }; /** From 171bc96557917d119f4fd6144028a161f56e1976 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 21 Dec 2024 22:01:49 +0100 Subject: [PATCH 02/15] npm run format --- src/CashuWallet.ts | 8 +++++--- src/crypto/nut-20.ts | 46 ++++++++++++++++++++++---------------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index c8abc85a..b45c9573 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -610,7 +610,7 @@ class CashuWallet { unit: this._unit, amount: amount, description: description, - pubkey: pubkey, + pubkey: pubkey }; return await this.mint.createMintQuote(mintQuotePayload); } @@ -652,11 +652,13 @@ class CashuWallet { counter, pubkey ); - const mintQuoteSignature = quotePrivkey ? signMintQuote(quotePrivkey, quote, blindedMessages) : undefined; + const mintQuoteSignature = quotePrivkey + ? signMintQuote(quotePrivkey, quote, blindedMessages) + : undefined; const mintPayload: MintPayload = { outputs: blindedMessages, quote: quote, - signature: mintQuoteSignature, + signature: mintQuoteSignature }; const { signatures } = await this.mint.mint(mintPayload); return this.constructProofs(signatures, blindingFactors, secrets, keyset); diff --git a/src/crypto/nut-20.ts b/src/crypto/nut-20.ts index 03d6270d..b0d1acd2 100644 --- a/src/crypto/nut-20.ts +++ b/src/crypto/nut-20.ts @@ -1,36 +1,36 @@ import { schnorr } from '@noble/curves/secp256k1'; -import { SerializedBlindedMessage } from "../model/types"; +import { SerializedBlindedMessage } from '../model/types'; import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { sha256 } from '@noble/hashes/sha256'; function constructMessage(quote: string, blindedMessages: SerializedBlindedMessage[]): Uint8Array { - let message = quote; - for (const blindedMessage of blindedMessages) { - message += blindedMessage.B_; - } - const msgbytes = new TextEncoder().encode(message); - return sha256(msgbytes); + let message = quote; + for (const blindedMessage of blindedMessages) { + message += blindedMessage.B_; + } + const msgbytes = new TextEncoder().encode(message); + return sha256(msgbytes); } export function signMintQuote( - privkey: string, - quote: string, - blindedMessages: SerializedBlindedMessage[], + privkey: string, + quote: string, + blindedMessages: SerializedBlindedMessage[] ): string { - const message = constructMessage(quote, blindedMessages); - const privkeyBytes = hexToBytes(privkey); - const signature = schnorr.sign(message, privkeyBytes); - return bytesToHex(signature); + const message = constructMessage(quote, blindedMessages); + const privkeyBytes = hexToBytes(privkey); + const signature = schnorr.sign(message, privkeyBytes); + return bytesToHex(signature); } export function verifyMintQuoteSignature( - pubkey: string, - quote: string, - blindedMessages: SerializedBlindedMessage[], - signature: string, + pubkey: string, + quote: string, + blindedMessages: SerializedBlindedMessage[], + signature: string ): boolean { - const sigbytes = hexToBytes(signature); - const pubkeyBytes = hexToBytes(pubkey); - const message = constructMessage(quote, blindedMessages); - return schnorr.verify(sigbytes, message, pubkeyBytes); -} \ No newline at end of file + const sigbytes = hexToBytes(signature); + const pubkeyBytes = hexToBytes(pubkey); + const message = constructMessage(quote, blindedMessages); + return schnorr.verify(sigbytes, message, pubkeyBytes); +} From 5e78dfbdb684d7f78cd073358ccd8362c6cfc660 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 21 Dec 2024 22:02:31 +0100 Subject: [PATCH 03/15] fix forbidden typing --- src/crypto/nut-20.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/nut-20.ts b/src/crypto/nut-20.ts index b0d1acd2..898c7ded 100644 --- a/src/crypto/nut-20.ts +++ b/src/crypto/nut-20.ts @@ -15,7 +15,7 @@ function constructMessage(quote: string, blindedMessages: SerializedBlindedMessa export function signMintQuote( privkey: string, quote: string, - blindedMessages: SerializedBlindedMessage[] + blindedMessages: Array ): string { const message = constructMessage(quote, blindedMessages); const privkeyBytes = hexToBytes(privkey); @@ -26,7 +26,7 @@ export function signMintQuote( export function verifyMintQuoteSignature( pubkey: string, quote: string, - blindedMessages: SerializedBlindedMessage[], + blindedMessages: Array, signature: string ): boolean { const sigbytes = hexToBytes(signature); From 47e20b62fd1015176b40350e50c46bb6875eb9e6 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sat, 21 Dec 2024 22:04:56 +0100 Subject: [PATCH 04/15] fix forbidden typing pt.2 --- src/crypto/nut-20.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/nut-20.ts b/src/crypto/nut-20.ts index 898c7ded..e9fcf6e9 100644 --- a/src/crypto/nut-20.ts +++ b/src/crypto/nut-20.ts @@ -3,7 +3,7 @@ import { SerializedBlindedMessage } from '../model/types'; import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { sha256 } from '@noble/hashes/sha256'; -function constructMessage(quote: string, blindedMessages: SerializedBlindedMessage[]): Uint8Array { +function constructMessage(quote: string, blindedMessages: Array): Uint8Array { let message = quote; for (const blindedMessage of blindedMessages) { message += blindedMessage.B_; From 22af1048a3343e7fe9bfac49d694cfbc72920f60 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 22 Dec 2024 10:35:48 +0100 Subject: [PATCH 05/15] quoteSignature tests --- test/quoteSignature.test.ts | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/quoteSignature.test.ts diff --git a/test/quoteSignature.test.ts b/test/quoteSignature.test.ts new file mode 100644 index 00000000..86660213 --- /dev/null +++ b/test/quoteSignature.test.ts @@ -0,0 +1,44 @@ +import { test, describe, expect } from 'vitest'; +import { MintPayload } from '../src/model/types/wallet/payloads'; +import { verifyMintQuoteSignature } from '../src/crypto/nut-20'; + +describe("mint quote signatures", () => { + test("valid signature verification", () => { + let mintRequest = { + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "outputs": [ + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79" + } + ], + "signature": "d4b386f21f7aa7172f0994ee6e4dd966539484247ea71c99b81b8e09b1bb2acbc0026a43c221fd773471dc30d6a32b04692e6837ddaccf0830a63128308e4ee0" + } as MintPayload; + const sig = mintRequest.signature!; + const quote = mintRequest.quote; + const pubkey = "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; + const blindedMessages = mintRequest.outputs; + expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(true); + }) +}); \ No newline at end of file From 4610f3acf9f9ce326cd998ddf316757021e239f4 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 22 Dec 2024 11:10:06 +0100 Subject: [PATCH 06/15] fix schnorr signature verification + add test signature creation --- src/crypto/nut-20.ts | 5 ++- test/quoteSignature.test.ts | 84 +++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/crypto/nut-20.ts b/src/crypto/nut-20.ts index e9fcf6e9..d7e89baf 100644 --- a/src/crypto/nut-20.ts +++ b/src/crypto/nut-20.ts @@ -30,7 +30,10 @@ export function verifyMintQuoteSignature( signature: string ): boolean { const sigbytes = hexToBytes(signature); - const pubkeyBytes = hexToBytes(pubkey); + let pubkeyBytes = hexToBytes(pubkey); + if (pubkeyBytes.length !== 33) + return false; + pubkeyBytes = pubkeyBytes.slice(1); const message = constructMessage(quote, blindedMessages); return schnorr.verify(sigbytes, message, pubkeyBytes); } diff --git a/test/quoteSignature.test.ts b/test/quoteSignature.test.ts index 86660213..e7479d7e 100644 --- a/test/quoteSignature.test.ts +++ b/test/quoteSignature.test.ts @@ -1,6 +1,8 @@ import { test, describe, expect } from 'vitest'; import { MintPayload } from '../src/model/types/wallet/payloads'; -import { verifyMintQuoteSignature } from '../src/crypto/nut-20'; +import { signMintQuote, verifyMintQuoteSignature } from '../src/crypto/nut-20'; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; +import { schnorr } from '@noble/curves/secp256k1'; describe("mint quote signatures", () => { test("valid signature verification", () => { @@ -40,5 +42,81 @@ describe("mint quote signatures", () => { const pubkey = "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; const blindedMessages = mintRequest.outputs; expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(true); - }) -}); \ No newline at end of file + }); + test("invalid signature verification", () => { + let mintRequest = { + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "outputs": [ + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79" + } + ], + "signature": "cb2b8e7ea69362dfe2a07093f2bbc319226db33db2ef686c940b5ec976bcbfc78df0cd35b3e998adf437b09ee2c950bd66dfe9eb64abd706e43ebc7c669c36c3" + } as MintPayload; + const sig = mintRequest.signature!; + const quote = mintRequest.quote; + const pubkey = "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; + const blindedMessages = mintRequest.outputs; + expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(false); + }); + test("signature creation", () => { + let mintRequest = { + "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", + "outputs": [ + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53" + }, + { + "amount": 1, + "id": "00456a94ab4e1c46", + "B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79" + } + ] + } as MintPayload; + const quote = mintRequest.quote; + const privkey = "d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; + const pubkey = "02" + bytesToHex(schnorr.getPublicKey(hexToBytes(privkey))); + const blindedMessages = mintRequest.outputs; + const signature = signMintQuote(privkey, quote, blindedMessages); + expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, signature)).toBe(true); + }); +}); From 5367a91abac06569bbd0114975df458efabe2122 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Thu, 26 Dec 2024 19:15:40 +0100 Subject: [PATCH 07/15] nut-20 mintinfo + error when nut-20 unsupported + npm format --- src/CashuWallet.ts | 6 + src/crypto/nut-20.ts | 10 +- src/model/MintInfo.ts | 7 +- src/model/types/mint/responses.ts | 4 + test/quoteSignature.test.ts | 232 +++++++++++++++--------------- 5 files changed, 137 insertions(+), 122 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index b45c9573..700f1d4e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -606,6 +606,12 @@ class CashuWallet { * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit */ async createMintQuote(amount: number, description?: string, pubkey?: string) { + if (pubkey) { + const { supported } = (await this.getMintInfo()).isSupported(20); + if (!supported) { + throw new Error('Mint does not support NUT-20'); + } + } const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount, diff --git a/src/crypto/nut-20.ts b/src/crypto/nut-20.ts index d7e89baf..18a3fa37 100644 --- a/src/crypto/nut-20.ts +++ b/src/crypto/nut-20.ts @@ -3,7 +3,10 @@ import { SerializedBlindedMessage } from '../model/types'; import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { sha256 } from '@noble/hashes/sha256'; -function constructMessage(quote: string, blindedMessages: Array): Uint8Array { +function constructMessage( + quote: string, + blindedMessages: Array +): Uint8Array { let message = quote; for (const blindedMessage of blindedMessages) { message += blindedMessage.B_; @@ -31,9 +34,8 @@ export function verifyMintQuoteSignature( ): boolean { const sigbytes = hexToBytes(signature); let pubkeyBytes = hexToBytes(pubkey); - if (pubkeyBytes.length !== 33) - return false; - pubkeyBytes = pubkeyBytes.slice(1); + if (pubkeyBytes.length !== 33) return false; + pubkeyBytes = pubkeyBytes.slice(1); const message = constructMessage(quote, blindedMessages); return schnorr.verify(sigbytes, message, pubkeyBytes); } diff --git a/src/model/MintInfo.ts b/src/model/MintInfo.ts index 8965a3f1..e76da4b2 100644 --- a/src/model/MintInfo.ts +++ b/src/model/MintInfo.ts @@ -8,7 +8,7 @@ export class MintInfo { } isSupported(num: 4 | 5): { disabled: boolean; params: Array }; - isSupported(num: 7 | 8 | 9 | 10 | 11 | 12 | 14): { supported: boolean }; + isSupported(num: 7 | 8 | 9 | 10 | 11 | 12 | 14 | 20): { supported: boolean }; isSupported(num: 17): { supported: boolean; params?: Array }; isSupported(num: 15): { supported: boolean; params?: Array }; isSupported(num: number) { @@ -23,7 +23,8 @@ export class MintInfo { case 10: case 11: case 12: - case 14: { + case 14: + case 20: { return this.checkGenericNut(num); } case 17: { @@ -37,7 +38,7 @@ export class MintInfo { } } } - private checkGenericNut(num: 7 | 8 | 9 | 10 | 11 | 12 | 14) { + private checkGenericNut(num: 7 | 8 | 9 | 10 | 11 | 12 | 14 | 20) { if (this._mintInfo.nuts[num]?.supported) { return { supported: true }; } diff --git a/src/model/types/mint/responses.ts b/src/model/types/mint/responses.ts index a641dedb..ab0954ff 100644 --- a/src/model/types/mint/responses.ts +++ b/src/model/types/mint/responses.ts @@ -103,6 +103,10 @@ export type GetInfoResponse = { // WebSockets supported: Array; }; + '20'?: { + // Locked Mint Quote + supported: boolean; + }; }; motd?: string; }; diff --git a/test/quoteSignature.test.ts b/test/quoteSignature.test.ts index e7479d7e..12cffefa 100644 --- a/test/quoteSignature.test.ts +++ b/test/quoteSignature.test.ts @@ -4,119 +4,121 @@ import { signMintQuote, verifyMintQuoteSignature } from '../src/crypto/nut-20'; import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; import { schnorr } from '@noble/curves/secp256k1'; -describe("mint quote signatures", () => { - test("valid signature verification", () => { - let mintRequest = { - "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", - "outputs": [ - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79" - } - ], - "signature": "d4b386f21f7aa7172f0994ee6e4dd966539484247ea71c99b81b8e09b1bb2acbc0026a43c221fd773471dc30d6a32b04692e6837ddaccf0830a63128308e4ee0" - } as MintPayload; - const sig = mintRequest.signature!; - const quote = mintRequest.quote; - const pubkey = "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; - const blindedMessages = mintRequest.outputs; - expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(true); - }); - test("invalid signature verification", () => { - let mintRequest = { - "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", - "outputs": [ - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79" - } - ], - "signature": "cb2b8e7ea69362dfe2a07093f2bbc319226db33db2ef686c940b5ec976bcbfc78df0cd35b3e998adf437b09ee2c950bd66dfe9eb64abd706e43ebc7c669c36c3" - } as MintPayload; - const sig = mintRequest.signature!; - const quote = mintRequest.quote; - const pubkey = "03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; - const blindedMessages = mintRequest.outputs; - expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(false); - }); - test("signature creation", () => { - let mintRequest = { - "quote": "9d745270-1405-46de-b5c5-e2762b4f5e00", - "outputs": [ - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53" - }, - { - "amount": 1, - "id": "00456a94ab4e1c46", - "B_": "02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79" - } - ] - } as MintPayload; - const quote = mintRequest.quote; - const privkey = "d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac"; - const pubkey = "02" + bytesToHex(schnorr.getPublicKey(hexToBytes(privkey))); - const blindedMessages = mintRequest.outputs; - const signature = signMintQuote(privkey, quote, blindedMessages); - expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, signature)).toBe(true); - }); +describe('mint quote signatures', () => { + test('valid signature verification', () => { + let mintRequest = { + quote: '9d745270-1405-46de-b5c5-e2762b4f5e00', + outputs: [ + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79' + } + ], + signature: + 'd4b386f21f7aa7172f0994ee6e4dd966539484247ea71c99b81b8e09b1bb2acbc0026a43c221fd773471dc30d6a32b04692e6837ddaccf0830a63128308e4ee0' + } as MintPayload; + const sig = mintRequest.signature!; + const quote = mintRequest.quote; + const pubkey = '03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac'; + const blindedMessages = mintRequest.outputs; + expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(true); + }); + test('invalid signature verification', () => { + let mintRequest = { + quote: '9d745270-1405-46de-b5c5-e2762b4f5e00', + outputs: [ + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79' + } + ], + signature: + 'cb2b8e7ea69362dfe2a07093f2bbc319226db33db2ef686c940b5ec976bcbfc78df0cd35b3e998adf437b09ee2c950bd66dfe9eb64abd706e43ebc7c669c36c3' + } as MintPayload; + const sig = mintRequest.signature!; + const quote = mintRequest.quote; + const pubkey = '03d56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac'; + const blindedMessages = mintRequest.outputs; + expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, sig)).toBe(false); + }); + test('signature creation', () => { + let mintRequest = { + quote: '9d745270-1405-46de-b5c5-e2762b4f5e00', + outputs: [ + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '0342e5bcc77f5b2a3c2afb40bb591a1e27da83cddc968abdc0ec4904201a201834' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '032fd3c4dc49a2844a89998d5e9d5b0f0b00dde9310063acb8a92e2fdafa4126d4' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '033b6fde50b6a0dfe61ad148fff167ad9cf8308ded5f6f6b2fe000a036c464c311' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '02be5a55f03e5c0aaea77595d574bce92c6d57a2a0fb2b5955c0b87e4520e06b53' + }, + { + amount: 1, + id: '00456a94ab4e1c46', + B_: '02209fc2873f28521cbdde7f7b3bb1521002463f5979686fd156f23fe6a8aa2b79' + } + ] + } as MintPayload; + const quote = mintRequest.quote; + const privkey = 'd56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac'; + const pubkey = '02' + bytesToHex(schnorr.getPublicKey(hexToBytes(privkey))); + const blindedMessages = mintRequest.outputs; + const signature = signMintQuote(privkey, quote, blindedMessages); + expect(verifyMintQuoteSignature(pubkey, quote, blindedMessages, signature)).toBe(true); + }); }); From 9d1da948a2f897b2c65bbbc44bdaace3eb2925cb Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Thu, 26 Dec 2024 22:50:09 +0100 Subject: [PATCH 08/15] new `createLockedMintQuote` method and `LockedMintQuote` type for `mintProofs` --- src/CashuWallet.ts | 51 +++++++++++++++++++++++++++------------- src/model/types/index.ts | 7 ++++-- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 700f1d4e..4c25954e 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -40,7 +40,8 @@ import { type SendResponse, type SerializedBlindedMessage, type SwapPayload, - type Token + type Token, + LockedMintQuote } from './model/types/index.js'; import { SubscriptionCanceller } from './model/types/wallet/websocket.js'; import { @@ -605,12 +606,27 @@ class CashuWallet { * @param pubkey optional public key to lock the quote to * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit */ - async createMintQuote(amount: number, description?: string, pubkey?: string) { - if (pubkey) { - const { supported } = (await this.getMintInfo()).isSupported(20); - if (!supported) { - throw new Error('Mint does not support NUT-20'); - } + async createMintQuote(amount: number, description?: string) { + const mintQuotePayload: MintQuotePayload = { + unit: this._unit, + amount: amount, + description: description, + }; + return await this.mint.createMintQuote(mintQuotePayload); + } + + /** + * Requests a mint quote from the mint that is locked to a public key. + * @param amount Amount requesting for mint. + * @param pubkey public key to lock the quote to + * @param description optional description for the mint quote + * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit. + * The quote will be locked to the specified `pubkey`. + */ + async createLockedMintQuote(amount: number, pubkey: string, description?: string) { + const { supported } = (await this.getMintInfo()).isSupported(20); + if (!supported) { + throw new Error('Mint does not support NUT-20'); } const mintQuotePayload: MintQuotePayload = { unit: this._unit, @@ -619,7 +635,7 @@ class CashuWallet { pubkey: pubkey }; return await this.mint.createMintQuote(mintQuotePayload); - } + } /** * Gets an existing mint quote from the mint. @@ -633,16 +649,17 @@ class CashuWallet { /** * Mint proofs for a given mint quote * @param amount amount to request - * @param quote ID of mint quote + * @param {string} quote - ID of mint quote (when quote is a string) + * @param {LockedMintQuote} quote - containing the quote ID and unlocking private key (when quote is a LockedMintQuote) * @param {MintProofOptions} [options] - Optional parameters for configuring the Mint Proof operation * @returns proofs */ async mintProofs( amount: number, - quote: string, + quote: string | LockedMintQuote, options?: MintProofOptions ): Promise> { - let { keysetId, proofsWeHave, outputAmounts, counter, pubkey, quotePrivkey } = options || {}; + let { keysetId, proofsWeHave, outputAmounts, counter, pubkey } = options || {}; const keyset = await this.getKeys(keysetId); if (!outputAmounts && proofsWeHave) { outputAmounts = { @@ -650,7 +667,6 @@ class CashuWallet { sendAmounts: [] }; } - const { blindedMessages, secrets, blindingFactors } = this.createRandomBlindedMessages( amount, keyset, @@ -658,12 +674,15 @@ class CashuWallet { counter, pubkey ); - const mintQuoteSignature = quotePrivkey - ? signMintQuote(quotePrivkey, quote, blindedMessages) - : undefined; + const mintQuoteSignature = typeof quote === 'string' + ? undefined + : signMintQuote(quote.privkey, quote.id, blindedMessages); + const quoteId = typeof quote === 'string' + ? quote + : quote.id; const mintPayload: MintPayload = { outputs: blindedMessages, - quote: quote, + quote: quoteId, signature: mintQuoteSignature }; const { signatures } = await this.mint.mint(mintPayload); diff --git a/src/model/types/index.ts b/src/model/types/index.ts index beb4b3d9..0b8bb27f 100644 --- a/src/model/types/index.ts +++ b/src/model/types/index.ts @@ -8,6 +8,11 @@ export type OutputAmounts = { keepAmounts?: Array; }; +export type LockedMintQuote = { + id: string; + privkey: string; +}; + /** * @param {ReceiveOptions} [options] - Optional configuration for token processing: * - `keysetId`: Override the default keyset ID with a custom one fetched from the `/keysets` endpoint. @@ -85,7 +90,6 @@ export type RestoreOptions = { * - `counter`: optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect * - `proofsWeHave`: optionally provide all currently stored proofs of this mint. Cashu-ts will use them to derive the optimal output amounts * - `pubkey`: optionally locks ecash to pubkey. Will not be deterministic, even if counter is set! - * - `privkey`: optional private key to unlock the mint quote. Generates a schnorr signature of the payload. */ export type MintProofOptions = { keysetId?: string; @@ -93,7 +97,6 @@ export type MintProofOptions = { proofsWeHave?: Array; counter?: number; pubkey?: string; - quotePrivkey?: string; }; /** From 39102505a14903e68e3aea5aea7c515ea364fb38 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Thu, 26 Dec 2024 22:51:55 +0100 Subject: [PATCH 09/15] npm run format --- src/CashuWallet.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index 4c25954e..bc5df065 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -610,11 +610,11 @@ class CashuWallet { const mintQuotePayload: MintQuotePayload = { unit: this._unit, amount: amount, - description: description, + description: description }; return await this.mint.createMintQuote(mintQuotePayload); } - + /** * Requests a mint quote from the mint that is locked to a public key. * @param amount Amount requesting for mint. @@ -635,7 +635,7 @@ class CashuWallet { pubkey: pubkey }; return await this.mint.createMintQuote(mintQuotePayload); - } + } /** * Gets an existing mint quote from the mint. @@ -650,7 +650,7 @@ class CashuWallet { * Mint proofs for a given mint quote * @param amount amount to request * @param {string} quote - ID of mint quote (when quote is a string) - * @param {LockedMintQuote} quote - containing the quote ID and unlocking private key (when quote is a LockedMintQuote) + * @param {LockedMintQuote} quote - containing the quote ID and unlocking private key (when quote is a LockedMintQuote) * @param {MintProofOptions} [options] - Optional parameters for configuring the Mint Proof operation * @returns proofs */ @@ -674,12 +674,11 @@ class CashuWallet { counter, pubkey ); - const mintQuoteSignature = typeof quote === 'string' - ? undefined - : signMintQuote(quote.privkey, quote.id, blindedMessages); - const quoteId = typeof quote === 'string' - ? quote - : quote.id; + const mintQuoteSignature = + typeof quote === 'string' + ? undefined + : signMintQuote(quote.privkey, quote.id, blindedMessages); + const quoteId = typeof quote === 'string' ? quote : quote.id; const mintPayload: MintPayload = { outputs: blindedMessages, quote: quoteId, From 468d433df51a5f38ccd017b24943e8f1976ad73a Mon Sep 17 00:00:00 2001 From: Egge Date: Fri, 27 Dec 2024 08:09:56 +0100 Subject: [PATCH 10/15] added type safety --- src/CashuWallet.ts | 63 +++++++++++++++++++++---------- src/model/types/mint/responses.ts | 2 + 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index bc5df065..d9dda048 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -41,7 +41,7 @@ import { type SerializedBlindedMessage, type SwapPayload, type Token, - LockedMintQuote + LockedMintQuoteReponse } from './model/types/index.js'; import { SubscriptionCanceller } from './model/types/wallet/websocket.js'; import { @@ -260,7 +260,7 @@ class CashuWallet { * @returns New token with newly created proofs, token entries that had errors */ async receive(token: string | Token, options?: ReceiveOptions): Promise> { - let { requireDleq, keysetId, outputAmounts, counter, pubkey, privkey } = options || {}; + const { requireDleq, keysetId, outputAmounts, counter, pubkey, privkey } = options || {}; if (typeof token === 'string') { token = getDecodedToken(token); @@ -299,7 +299,7 @@ class CashuWallet { * @returns {SendResponse} */ async send(amount: number, proofs: Array, options?: SendOptions): Promise { - let { + const { proofsWeHave, offline, includeFees, @@ -572,7 +572,7 @@ class CashuWallet { count: number, options?: RestoreOptions ): Promise<{ proofs: Array }> { - let { keysetId } = options || {}; + const { keysetId } = options || {}; const keys = await this.getKeys(keysetId); if (!this._seed) { throw new Error('CashuWallet must be initialized with a seed to use restore'); @@ -623,7 +623,11 @@ class CashuWallet { * @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit. * The quote will be locked to the specified `pubkey`. */ - async createLockedMintQuote(amount: number, pubkey: string, description?: string) { + async createLockedMintQuote( + amount: number, + pubkey: string, + description?: string + ): Promise { const { supported } = (await this.getMintInfo()).isSupported(20); if (!supported) { throw new Error('Mint does not support NUT-20'); @@ -634,7 +638,11 @@ class CashuWallet { description: description, pubkey: pubkey }; - return await this.mint.createMintQuote(mintQuotePayload); + const res = await this.mint.createMintQuote(mintQuotePayload); + if (!res.pubkey) { + throw new Error('Mint returned unlocked mint quote'); + } + return res as LockedMintQuoteReponse; } /** @@ -656,10 +664,20 @@ class CashuWallet { */ async mintProofs( amount: number, - quote: string | LockedMintQuote, + quote: MintQuoteResponse, + options: MintProofOptions & { privateKey: string } + ): Promise>; + async mintProofs( + amount: number, + quote: string, options?: MintProofOptions + ): Promise>; + async mintProofs( + amount: number, + quote: string | MintQuoteResponse, + options?: MintProofOptions & { privateKey?: string } ): Promise> { - let { keysetId, proofsWeHave, outputAmounts, counter, pubkey } = options || {}; + let { keysetId, proofsWeHave, outputAmounts, counter, pubkey, privateKey } = options || {}; const keyset = await this.getKeys(keysetId); if (!outputAmounts && proofsWeHave) { outputAmounts = { @@ -674,16 +692,23 @@ class CashuWallet { counter, pubkey ); - const mintQuoteSignature = - typeof quote === 'string' - ? undefined - : signMintQuote(quote.privkey, quote.id, blindedMessages); - const quoteId = typeof quote === 'string' ? quote : quote.id; - const mintPayload: MintPayload = { - outputs: blindedMessages, - quote: quoteId, - signature: mintQuoteSignature - }; + let mintPayload: MintPayload; + if (typeof quote !== 'string') { + if (!privateKey) { + throw new Error('Can not sign locked quote without private key'); + } + const mintQuoteSignature = signMintQuote(privateKey, quote.quote, blindedMessages); + mintPayload = { + outputs: blindedMessages, + quote: quote.quote, + signature: mintQuoteSignature + }; + } else { + mintPayload = { + outputs: blindedMessages, + quote: quote + }; + } const { signatures } = await this.mint.mint(mintPayload); return this.constructProofs(signatures, blindingFactors, secrets, keyset); } @@ -725,7 +750,7 @@ class CashuWallet { proofsToSend: Array, options?: MeltProofOptions ): Promise { - let { keysetId, counter, privkey } = options || {}; + const { keysetId, counter, privkey } = options || {}; const keys = await this.getKeys(keysetId); const { blindedMessages, secrets, blindingFactors } = this.createBlankOutputs( sumProofs(proofsToSend) - meltQuote.amount, diff --git a/src/model/types/mint/responses.ts b/src/model/types/mint/responses.ts index ab0954ff..2b1e9806 100644 --- a/src/model/types/mint/responses.ts +++ b/src/model/types/mint/responses.ts @@ -188,6 +188,8 @@ export type MintQuoteResponse = { pubkey?: string; } & ApiError; +export type LockedMintQuoteReponse = MintQuoteResponse & { pubkey: string }; + /** * Response from the mint after requesting a mint */ From 3110b75e3e08cf94fc63696e6a9c2cf06a114c5a Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Fri, 27 Dec 2024 09:25:12 +0100 Subject: [PATCH 11/15] fix typo --- src/CashuWallet.ts | 6 +++--- src/model/types/mint/responses.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/CashuWallet.ts b/src/CashuWallet.ts index d9dda048..1d1b7e70 100644 --- a/src/CashuWallet.ts +++ b/src/CashuWallet.ts @@ -41,7 +41,7 @@ import { type SerializedBlindedMessage, type SwapPayload, type Token, - LockedMintQuoteReponse + LockedMintQuoteResponse } from './model/types/index.js'; import { SubscriptionCanceller } from './model/types/wallet/websocket.js'; import { @@ -627,7 +627,7 @@ class CashuWallet { amount: number, pubkey: string, description?: string - ): Promise { + ): Promise { const { supported } = (await this.getMintInfo()).isSupported(20); if (!supported) { throw new Error('Mint does not support NUT-20'); @@ -642,7 +642,7 @@ class CashuWallet { if (!res.pubkey) { throw new Error('Mint returned unlocked mint quote'); } - return res as LockedMintQuoteReponse; + return res as LockedMintQuoteResponse; } /** diff --git a/src/model/types/mint/responses.ts b/src/model/types/mint/responses.ts index 2b1e9806..69ac1a14 100644 --- a/src/model/types/mint/responses.ts +++ b/src/model/types/mint/responses.ts @@ -188,7 +188,7 @@ export type MintQuoteResponse = { pubkey?: string; } & ApiError; -export type LockedMintQuoteReponse = MintQuoteResponse & { pubkey: string }; +export type LockedMintQuoteResponse = MintQuoteResponse & { pubkey: string }; /** * Response from the mint after requesting a mint From 8ec0629e9722cbf4c46ebc24a9c3c519690e914b Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 29 Dec 2024 00:20:41 +0100 Subject: [PATCH 12/15] integration test --- test/integration.test.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/test/integration.test.ts b/test/integration.test.ts index 7bf61f4a..9cb6cbd6 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -4,8 +4,8 @@ import { CashuWallet } from '../src/CashuWallet.js'; import dns from 'node:dns'; import { test, describe, expect } from 'vitest'; import { vi } from 'vitest'; -import { secp256k1 } from '@noble/curves/secp256k1'; -import { bytesToHex } from '@noble/curves/abstract/utils'; +import { schnorr, secp256k1 } from '@noble/curves/secp256k1'; +import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { CheckStateEnum, MeltQuoteState, @@ -358,6 +358,19 @@ describe('mint api', () => { }); mint.disconnectWebSocket(); }, 10000); + test('mint with signed quote and payload', async () => { + const mint = new CashuMint(mintUrl); + const wallet = new CashuWallet(mint); + + const privkey = 'd56ce4e446a85bbdaa547b4ec2b073d40ff802831352b8272b7dd7a4de5a7cac'; + const pubkey = '02' + bytesToHex(schnorr.getPublicKey(hexToBytes(privkey))); + + const quote = await wallet.createLockedMintQuote(63, pubkey); + const proofs = await wallet.mintProofs(63, quote, { privateKey: privkey }); + + expect(proofs).toBeDefined(); + expect(proofs.length).toBeGreaterThan(0); + }); }); describe('dleq', () => { test('mint and check dleq', async () => { From 52afd4c490355f87d8afb1fdc926216462aaab94 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 29 Dec 2024 11:01:48 +0100 Subject: [PATCH 13/15] github workflow new nutshell --- .github/workflows/nutshell-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index ef25b0c1..9062e6bb 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Pull and start mint run: | - docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.2 poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.3 poetry run mint - name: Check running containers run: docker ps From e35a1a742bdb5a93862b2ae3a9bebb910a174222 Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Sun, 29 Dec 2024 11:07:57 +0100 Subject: [PATCH 14/15] github workflow nutshell latest --- .github/workflows/nutshell-integration.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 9062e6bb..8da186e1 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -8,8 +8,7 @@ jobs: steps: - name: Pull and start mint run: | - docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.3 poetry run mint - + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:latest poetry run mint - name: Check running containers run: docker ps From be1c9c3e164c1aae5e6db9b07075766c782cefca Mon Sep 17 00:00:00 2001 From: lollerfirst Date: Fri, 17 Jan 2025 11:46:35 +0100 Subject: [PATCH 15/15] nutshell 0.16.4 --- .github/workflows/nutshell-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nutshell-integration.yml b/.github/workflows/nutshell-integration.yml index 8da186e1..7ca351ee 100644 --- a/.github/workflows/nutshell-integration.yml +++ b/.github/workflows/nutshell-integration.yml @@ -8,7 +8,7 @@ jobs: steps: - name: Pull and start mint run: | - docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:latest poetry run mint + docker run -d -p 3338:3338 --name nutshell -e MINT_LIGHTNING_BACKEND=FakeWallet -e MINT_INPUT_FEE_PPK=100 -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.16.4 poetry run mint - name: Check running containers run: docker ps