-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for NUT-12 #200
Support for NUT-12 #200
Conversation
src/CashuWallet.ts
Outdated
if (verifyDLEQ) { | ||
if (dleq == undefined) { | ||
throw new Error('DLEQ verification required, but none found'); | ||
} | ||
if (!verifyDLEQProof_reblind(secret, dleq, proof.C, A)) { | ||
throw new Error('DLEQ verification failed'); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (verifyDLEQ) { | |
if (dleq == undefined) { | |
throw new Error('DLEQ verification required, but none found'); | |
} | |
if (!verifyDLEQProof_reblind(secret, dleq, proof.C, A)) { | |
throw new Error('DLEQ verification failed'); | |
} | |
} | |
if (verifyDLEQ) { | |
if (dleq == undefined) { | |
throw new Error('DLEQ verification required, but none found'); | |
} | |
if (!verifyDLEQProof_reblind(secret, dleq, proof.C, A)) { | |
throw new Error('DLEQ verification failed'); | |
} | |
} |
It might be dangerous to throw an error here, we might still want to keep the proofs, even if the verification failed. I have no good solution for this case though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be dangerous to throw an error here
Agreed.
What if cashu-ts just constructs the dleq, then its up to the implementer to verify and handle failures?
The other idea I had was have some sort of flag either on the Proof
or in the return value of constructProofs
that indicates the verification failed, but that gets gross:
/**
* represents a single Cashu proof.
*/
export type Proof = {
...
/**
* DLEQ proof
*/
dleq?: SerializedDLEQ;
/**
* Is the DLEQ proof valid
*/
dleqValid?: boolan;
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like @gudnuf 's idea
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think @callebtc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nostr-tools uses a Symbol to signal verified events: https://github.com/nbd-wtf/nostr-tools/blob/master/core.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Egge21M What changes from a normal boolean flag?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Symbols are a bit "safer" than regular properties. The are not enumerable and have to be accessed very purposefully. Also, because they are not enumerable they will be excluded by default by methods like JSON.stringify
… returned a DLEQ and it was verified correctly.
Unfortunately it seems we need to fix cashu-crypto-ts's package.json again... |
So the integration tests with Nutshell work fine. I might try and other ones better cover cases where DLEQ is not verified correctly. I couldn't create normal tests (in |
Great stuff! LMK when this is ready for review |
FYI I tried using a symbol for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work and a fantastic start! I added some comments, but i think we are mostly there
src/CashuWallet.ts
Outdated
} | ||
): Promise<SendResponse> { | ||
if (options?.includeDleq ?? false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use nullish coalescing here?
src/CashuWallet.ts
Outdated
let { keep, send } = await this.swap(amount, sendProofs, options); | ||
keep = keepProofsSelect.concat(keep); | ||
|
||
// strip dleq if explicitly told so |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does !options?.includeDleq
have two meanings in this case? Ignoring proofs without dleqs when true
and stripping dleqs when false
? Maybe we should not overload this option to be more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea that's what I intended and what made most sense to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I see. I think most people will assume that not-passing an optional flag is equal to setting the boolean to false. Diverging from that needs to be documented very clearly I believe. However I would love to get some implementers thoughts on this. @callebtc @gudnuf could you leave your 2 sats on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you maybe explain the usecase to me? WWhy would someone decide to strip DLEQs when sending to another wallet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it just clicked for me... sorry for all the confusion. includeDleq
signals wether or not a token sent should include DLEQs for all included proofs, THEREFORE:
if (options?.includeDleq) -> Filter proofs for proofs with DLEQ, do not strip them
if (!options?.includeDleq) This is not an explicit false, simply NOT an explicit true -> Include all proofs, strip all DLEQs
src/utils.ts
Outdated
@@ -292,7 +321,15 @@ export function handleTokens(token: string): Token { | |||
secret: p.s, | |||
C: bytesToHex(p.c), | |||
amount: p.a, | |||
id: bytesToHex(t.i) | |||
id: bytesToHex(t.i), | |||
dleq: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can avoid dleq:undefined
being present on every token without dleqs by doing something like
proofs.push({
secret: p.s,
C: bytesToHex(p.c),
amount: p.a,
id: bytesToHex(t.i),
...(p.d && {
dleq: {
e: bytesToHex(p.d.e),
s: bytesToHex(p.d.s),
r: bytesToHex(p.d.r)
} as SerializedDLEQ
})
});
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also use an if-statement, as it might be more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried using this elegant solution in the place of almost all other ugly dleq == undefined ? undefined : ...
check it out.
src/CashuWallet.ts
Outdated
* @param token The token subject to the verification | ||
* @param keys The Mint's keyset to be used for verification | ||
*/ | ||
private requireDLEQ(token: Token, keys: MintKeys) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be missing something here, but it seems like this function does not rely on any of the instance state. Therefore I would like to move it to utils.ts
and export it to the public API.
Functions that do not return anything, but instead throw are hard to work with IMHO. Would you not agree that it would be clearer to rename this to hasValidDleq
, make it take a SINGLE proof, and make it return true
is all pass and false
if dleq are either missing or invalid?
We could then use this util to build filters like proofs.filter(p => hasValidDleq(p, keys)
or a util to replace requireDleq
with a better best-case running time like
if (proofs.some(p => !hasValidDelq(p, keys))) {
throw new Error("Proofs contain invalid DLEQ")
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doing this right now. Should I let hasValidDleq
throw in case there is no matching key from the keyset proof.id
for a particular proof amount? Or should I just return false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes sense to throw in this case. false
would indicate an invalid DLEQ, while in this case it's more than just that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added a test for hasValidDleq
as well.
src/CashuWallet.ts
Outdated
let { keep, send } = await this.swap(amount, sendProofs, options); | ||
keep = keepProofsSelect.concat(keep); | ||
|
||
// strip dleq if explicitly told so |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you maybe explain the usecase to me? WWhy would someone decide to strip DLEQs when sending to another wallet?
src/model/BlindedSignature.ts
Outdated
id: this.id, | ||
amount: this.amount, | ||
C_: this.C_.toHex(true), | ||
dleq: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the spread syntax here too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Also I am responding here to your other comment about "why would someone strip the DLEQs when sending to Carol". One might try to get a smaller size cashuB token for example. In general if it's not required by Carol why include it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Fixes: #156
Changes
dleq
anddleqValid
fields toProof
.dleq
holds aSerializedDLEQ
proof, whiledleqValid
, if present, signals whether it has been correctly verified.CashuWallet.meltProofs
andCashuWallet.createSwapPayload
strip thedleq
field off of eachProof
, if there is any. (No privacy leak).CashuWallet.construct_proofs
now unpacks and verifies DLEQ proofs returned by the Mint, if any. It will createProof
with:dleqValid == true
if the DLEQ is present and verifieddleqValid == undefined
if DLEQ not presentdleqValid == false
if DLEQ present and verification failed.includeDleq
toCashuWallet.send
:includeDleq == true
, makes sure the selectedsend
proofs each have a defineddleq
field;includeDleq == false
, strip off thedleq
field fromsend
;includeDleq == undefined
, do not intervene.requireDleq
toCashuWallet.receive
:requireDleq == true
, verifies each proof's dleq for correctness.requireDleq == false || requireDleq == undefined
, does not verify dleq.CashuWallet.requireDLEQ
. Separates the logic for requiring valid dleqs from a token from the functionreceive
.numberToHexPadded64
to the utilities. This function takes in abigint
and returns an a hex string prefixed to 64 characters with '0'.PR Tasks
npm run test
--> no failing unit testsnpm run format