Skip to content

Commit

Permalink
more refactoring + cleanup
Browse files Browse the repository at this point in the history
more refactoring

more refactoring
  • Loading branch information
Egge21M committed Dec 3, 2024
1 parent 7acb6cb commit ea91e93
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 177 deletions.
154 changes: 19 additions & 135 deletions src/CashuWallet.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { CashuMint } from './CashuMint.js';
import {
type MeltPayload,
Expand All @@ -11,33 +10,28 @@ import {
type MintQuotePayload,
type MeltQuotePayload,
type SendResponse,
type SerializedBlindedMessage,
type Token,
SerializedBlindedSignature,
GetInfoResponse,
OutputAmounts,
ProofState,
MintQuoteResponse,
MintQuoteState,
MeltQuoteState,
SerializedDLEQ
SwapTransaction
} from './model/types/index.js';
import {
getDecodedToken,
splitAmount,
sumProofs,
getKeepAmounts,
numberToHexPadded64,
hasValidDleq,
stripDleq
} from './utils.js';
import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common';
import { constructProofFromPromise, serializeProof } from '@cashu/crypto/modules/client';
import { serializeProof } from '@cashu/crypto/modules/client';
import { getSignedProofs } from '@cashu/crypto/modules/client/NUT11';
import { type Proof as NUT11Proof, DLEQ } from '@cashu/crypto/modules/common/index';
import { type Proof as NUT11Proof } from '@cashu/crypto/modules/common/index';
import { SubscriptionCanceller } from './model/types/wallet/websocket.js';
import { verifyDLEQProof_reblind } from '@cashu/crypto/modules/client/NUT12';
import { SwapTransaction } from './model/types/wallet/blinding.js';
import { BlindingData } from './model/BlindingData.js';
/**
* The default number of proofs per denomination to keep in a wallet.
Expand Down Expand Up @@ -571,7 +565,7 @@ class CashuWallet {
options?.customBlindingData
);
const { signatures } = await this.mint.swap(swapTransaction.payload);
const swapProofs = this.constructProofs(signatures, swapTransaction.blindingData, keyset);
const swapProofs = swapTransaction.blindingData.map((d, i) => d.toProof(signatures[i], keyset));
const splitProofsToKeep: Array<Proof> = [];
const splitProofsToSend: Array<Proof> = [];
swapProofs.forEach((p, i) => {
Expand Down Expand Up @@ -620,7 +614,7 @@ class CashuWallet {

const validData = blindingData.filter((d) => outputs.some((o) => d.blindedMessage.B_ === o.B_));
return {
proofs: this.constructProofs(promises, validData, keys)
proofs: validData.map((d, i) => d.toProof(promises[i], keys))
};
}

Expand Down Expand Up @@ -683,19 +677,19 @@ class CashuWallet {
};
}

const blindingData = this.createRandomBlindedMessages(
const blindingData = this.createBlindedMessages(
amount,
keyset,
options?.outputAmounts?.keepAmounts,
options?.counter,
options?.pubkey
options?.pubkey,
options?.outputAmounts?.keepAmounts
);
const mintPayload: MintPayload = {
outputs: blindingData.map((d) => d.blindedMessage),
quote: quote
};
const { signatures } = await this.mint.mint(mintPayload);
return this.constructProofs(signatures, blindingData, keyset);
return blindingData.map((d, i) => d.toProof(signatures[i], keyset));
}

/**
Expand Down Expand Up @@ -769,13 +763,9 @@ class CashuWallet {
outputs: blindingData.map((d) => d.blindedMessage)
};
const meltResponse = await this.mint.melt(meltPayload);
let change: Array<Proof> = [];
if (meltResponse.change) {
change = this.constructProofs(meltResponse.change, blindingData, keys);
}
return {
quote: meltResponse,
change: change
change: meltResponse.change?.map((s, i) => blindingData[i].toProof(s, keys)) ?? []
};
}

Expand Down Expand Up @@ -815,53 +805,28 @@ class CashuWallet {

if (customBlindingData?.keep) {
keepBlindingData = customBlindingData.keep;
} else if (this._seed && counter) {
keepBlindingData = BlindingData.createDeterministicData(
keepAmount,
this._seed,
counter,
keyset,
outputAmounts?.keepAmounts
);
counter = counter + keepBlindingData.length;
} else if (pubkey) {
keepBlindingData = BlindingData.createP2PKData(
pubkey,
keepAmount,
keyset,
outputAmounts?.keepAmounts
);
} else {
keepBlindingData = BlindingData.createRandomData(
keepBlindingData = this.createBlindedMessages(
keepAmount,
keyset,
counter,
pubkey,
outputAmounts?.keepAmounts
);
}

if (customBlindingData?.send) {
sendBlindingData = customBlindingData.send;
} else if (counter || counter === 0) {
if (!this._seed) {
throw new Error('cannot create deterministic messages without seed');
}
sendBlindingData = BlindingData.createDeterministicData(
} else {
sendBlindingData = this.createBlindedMessages(
amount,
this._seed,
counter,
keyset,
outputAmounts?.sendAmounts
);
counter = counter + sendBlindingData.length;
} else if (pubkey) {
sendBlindingData = BlindingData.createP2PKData(
counter,
pubkey,
amount,
keyset,
outputAmounts?.sendAmounts
);
} else {
sendBlindingData = BlindingData.createRandomData(amount, keyset, outputAmounts?.sendAmounts);
}

if (privkey) {
proofsToSend = getSignedProofs(
proofsToSend.map((p: Proof) => {
Expand Down Expand Up @@ -1067,30 +1032,6 @@ class CashuWallet {
};
}

/**
* Creates blinded messages for a given amount
* @param amount amount to create blinded messages for
* @param split optional preference for splitting proofs into specific amounts. overrides amount param
* @param keyksetId? override the keysetId derived from the current mintKeys with a custom one. This should be a keyset that was fetched from the `/keysets` endpoint
* @param counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect
* @param pubkey? optionally locks ecash to pubkey. Will not be deterministic, even if counter is set!
* @returns blinded messages, secrets, rs, and amounts
*/
private createRandomBlindedMessages(
amount: number,
keyset: MintKeys,
split?: Array<number>,
counter?: number,
pubkey?: string
): Array<{
blindingFactor: bigint;
secret: Uint8Array;
blindedMessage: SerializedBlindedMessage;
}> {
const amounts = splitAmount(amount, keyset.keys, split);
return this.createBlindedMessages(amount, keyset, counter, pubkey, amounts);
}

/**
* Creates blinded messages for a according to @param amounts
* @param amount array of amounts to create blinded messages for
Expand Down Expand Up @@ -1138,11 +1079,7 @@ class CashuWallet {
amount: number,
keyset: MintKeys,
counter?: number
): Array<{
blindingFactor: bigint;
secret: Uint8Array;
blindedMessage: SerializedBlindedMessage;
}> {
): Array<BlindingData> {
let count = Math.ceil(Math.log2(amount)) || 1;
//Prevent count from being -Infinity
if (count < 0) {
Expand All @@ -1151,59 +1088,6 @@ class CashuWallet {
const amounts = count ? Array(count).fill(1) : [];
return this.createBlindedMessages(amount, keyset, counter, undefined, amounts);
}

/**
* construct proofs from @params promises, @params rs, @params secrets, and @params keyset
* @param promises array of serialized blinded signatures
* @param rs arrays of binding factors
* @param secrets array of secrets
* @param keyset mint keyset
* @returns array of serialized proofs
*/
private constructProofs(
promises: Array<SerializedBlindedSignature>,
blindingData: Array<{
blindingFactor: bigint;
secret: Uint8Array;
blindedMessage: SerializedBlindedMessage;
}>,
keyset: MintKeys
): Array<Proof> {
return promises.map((p: SerializedBlindedSignature, i: number) => {
const dleq =
p.dleq == undefined
? undefined
: ({
s: hexToBytes(p.dleq.s),
e: hexToBytes(p.dleq.e),
r: blindingData[i].blindingFactor
} as DLEQ);
const blindSignature = {
id: p.id,
amount: p.amount,
C_: pointFromHex(p.C_),
dleq: dleq
};
const r = blindingData[i].blindingFactor;
const secret = blindingData[i].secret;
const A = pointFromHex(keyset.keys[p.amount]);
const proof = constructProofFromPromise(blindSignature, r, secret, A);
const serializedProof = {
...serializeProof(proof),
...(dleq && {
dleqValid: verifyDLEQProof_reblind(secret, dleq, proof.C, A)
}),
...(dleq && {
dleq: {
s: bytesToHex(dleq.s),
e: bytesToHex(dleq.e),
r: numberToHexPadded64(dleq.r ?? BigInt(0))
} as SerializedDLEQ
})
} as Proof;
return serializedProof;
});
}
}

export { CashuWallet };
8 changes: 0 additions & 8 deletions src/model/types/wallet/blinding.ts

This file was deleted.

37 changes: 19 additions & 18 deletions src/model/types/wallet/payloads.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import { BlindingData } from '../../BlindingData';
import { Proof } from './index';

/**
* Data that the library needs to hold in memory while it awaits the blinded signatures for the mint. It is later used for unblinding the signatures.
*/
export type BlindingData = {
/**
* Blinded messages sent to the mint for signing.
*/
blindedMessages: Array<SerializedBlindedMessage>;
/**
* secrets, kept client side for constructing proofs later.
*/
secrets: Array<Uint8Array>;
/**
* Blinding factor used for blinding messages and unblinding signatures after they are received from the mint.
*/
blindingFactors: Array<bigint>;
};

/**
* Payload that needs to be sent to the mint when melting. Includes Return for overpaid fees
*/
Expand Down Expand Up @@ -113,3 +96,21 @@ export type SerializedBlindedMessage = {
*/
id: string;
};

/**
* includes all data required to swap inputs for outputs and construct proofs from them.
*/
export type SwapTransaction = {
/**
* payload that will be sent to the mint for a swap
*/
payload: SwapPayload;
/**
* blinding data required to construct proofs
*/
blindingData: Array<BlindingData>;
/**
* list of booleans to determine which proofs to keep
*/
keepVector: Array<boolean>;
};
11 changes: 0 additions & 11 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
encodeUint8toBase64Url
} from './base64.js';
import {
BlindingData,
DeprecatedToken,
Keys,
MintKeys,
Expand Down Expand Up @@ -71,16 +70,6 @@ export function splitAmount(
return split.sort((a, b) => (order === 'desc' ? b - a : a - b));
}

export function mergeBlindingData(...data: Array<BlindingData>): BlindingData {
const mergedData: BlindingData = { blindedMessages: [], blindingFactors: [], secrets: [] };
data.forEach((d) => {
mergedData.secrets.push(...d.secrets);
mergedData.blindingFactors.push(...d.blindingFactors);
mergedData.blindedMessages.push(...d.blindedMessages);
});
return mergedData;
}

/**
* Creates a list of amounts to keep based on the proofs we have and the proofs we want to reach.
* @param proofsWeHave complete set of proofs stored (from current mint)
Expand Down
2 changes: 0 additions & 2 deletions test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import {
import ws from 'ws';
import { injectWebSocketImpl } from '../src/ws.js';
import {
deriveKeysetId,
getEncodedToken,
getEncodedTokenV4,
hexToNumber,
mergeBlindingData,
numberToHexPadded64,
sumProofs
} from '../src/utils.js';
Expand Down
4 changes: 1 addition & 3 deletions test/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import {
MintQuoteResponse,
MintQuoteState
} from '../src/model/types/index.js';
import { getDecodedToken, mergeBlindingData, splitAmount } from '../src/utils.js';
import { Proof } from '@cashu/crypto/modules/common';
import { getDecodedToken } from '../src/utils.js';
import { Server, WebSocket } from 'mock-socket';
import { injectWebSocketImpl } from '../src/ws.js';
import { BlindingData } from '../src/model/BlindingData.js';

injectWebSocketImpl(WebSocket);

Expand Down

0 comments on commit ea91e93

Please sign in to comment.