New p2pk and htlc rules, sig all support#24
Conversation
📝 WalkthroughWalkthroughRenames P2PkBuilder → P2PKBuilder across code and tests; adds RefundSignatureThreshold and validation to P2PKBuilder; extends P2PK/HTLC proof secrets with refund-path APIs, nullable witness returns, and signing helpers; introduces SigAllHandler for SIG_ALL multi-proof signing/verification; adds Nut10ProofSecret value equality and minor serialization tweaks. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant SigAllHandler
participant P2PKProofSecret
participant HTLCProofSecret
Client->>SigAllHandler: TrySign(proofs, privKeys, blindedMessages, HTLCPreimage?, meltQuoteId?)
SigAllHandler->>SigAllHandler: ValidateFirstProof() / GetMessageToSign()
alt first proof is P2PK
SigAllHandler->>P2PKProofSecret: GenerateWitness(message, privKeys)
P2PKProofSecret->>P2PKProofSecret: TrySignPath(normal allowed keys)
alt normal path succeeds
P2PKProofSecret-->>SigAllHandler: P2PKWitness
else normal fails
P2PKProofSecret->>P2PKProofSecret: GetAllowedRefundPubkeys()
P2PKProofSecret->>P2PKProofSecret: TrySignPath(refund keys)
alt refund succeeds
P2PKProofSecret-->>SigAllHandler: P2PKWitness
else
P2PKProofSecret-->>SigAllHandler: null
end
end
else first proof is HTLC
SigAllHandler->>HTLCProofSecret: GenerateWitness(message, privKeys, preimage?)
HTLCProofSecret->>HTLCProofSecret: TrySignPath(normal allowed keys)
alt normal path succeeds
HTLCProofSecret-->>SigAllHandler: HTLCWitness
else
HTLCProofSecret->>HTLCProofSecret: GetAllowedRefundPubkeys()
HTLCProofSecret->>HTLCProofSecret: TrySignPath(refund keys)
alt refund succeeds
HTLCProofSecret-->>SigAllHandler: HTLCWitness
else
HTLCProofSecret-->>SigAllHandler: null
end
end
end
alt witness produced
SigAllHandler->>SigAllHandler: Serialize witness JSON
SigAllHandler-->>Client: success + witness
else no witness
SigAllHandler-->>Client: failure + null
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
DotNut/HTLCBuilder.cs (2)
34-43: MissingRefundSignatureThresholdin Load method.The
Loadmethod copies properties frominnerbuilderbut doesn't include the newly addedRefundSignatureThreshold. This could cause HTLC secrets with refund thresholds to lose that information when loaded.🔎 Proposed fix
return new HTLCBuilder() { HashLock = hashLock, Lock = innerbuilder.Lock, Pubkeys = innerbuilder.Pubkeys, RefundPubkeys = innerbuilder.RefundPubkeys, SignatureThreshold = innerbuilder.SignatureThreshold, + RefundSignatureThreshold = innerbuilder.RefundSignatureThreshold, SigFlag = innerbuilder.SigFlag, Nonce = innerbuilder.Nonce };
53-61: MissingRefundSignatureThresholdin Build method.Similarly, the
Buildmethod creates a newP2PKBuilderbut doesn't propagateRefundSignatureThreshold. This means HTLC secrets built through this builder won't include then_sigs_refundtag even if set.🔎 Proposed fix
var innerBuilder = new P2PKBuilder() { Lock = Lock, Pubkeys = Pubkeys.ToArray(), RefundPubkeys = RefundPubkeys, SignatureThreshold = SignatureThreshold, + RefundSignatureThreshold = RefundSignatureThreshold, SigFlag = SigFlag, Nonce = Nonce };DotNut/HTLCProofSecret.cs (1)
198-210: Potential null reference whenPreimageis null.Since
HTLCWitness.Preimageis now nullable, callingVerifyPreimage(htlcWitness.Preimage)at line 204 could passnulltoVerifyPreimage(string preimage). TheVerifyPreimagemethod at line 102 callsConvert.FromHexString(preimage)which will throw ifpreimageis null.🔎 Proposed fix
public override bool VerifyWitnessHash(byte[] hash, P2PKWitness witness) { if (witness is not HTLCWitness htlcWitness) { return false; } - if (!VerifyPreimage(htlcWitness.Preimage)) + if (htlcWitness.Preimage is null || !VerifyPreimage(htlcWitness.Preimage)) { return false; } return base.VerifyWitnessHash(hash, witness); }
🧹 Nitpick comments (4)
DotNut/SigAllHandler.cs (2)
10-14: Consider initializing collection properties to avoid null reference issues.The
Proofs,PrivKeys, andBlindedMessagesproperties are not initialized and could be null. WhileTrySigndoes null-check them, initializing to empty collections would be more defensive.🔎 Proposed fix
- public List<Proof> Proofs { get; set; } - public List<PrivKey> PrivKeys { get; set; } - public List<BlindedMessage> BlindedMessages { get; set; } + public List<Proof> Proofs { get; set; } = new(); + public List<PrivKey> PrivKeys { get; set; } = new(); + public List<BlindedMessage> BlindedMessages { get; set; } = new();
151-154: Avoid catching genericException.Catching all exceptions masks potential bugs and makes debugging harder. Consider catching specific exceptions that
GetMessageToSignmight throw.🔎 Proposed fix
- catch(Exception ex) + catch (ArgumentException) { return false; }DotNut/P2PKProofSecret.cs (2)
89-109: Consider using aList<string>to avoid O(n²) array allocations.Using
Append().ToArray()inside a loop creates a new array on each iteration. For multi-sig scenarios with many keys, this could be inefficient.🔎 Proposed optimization
private (bool IsValid, P2PKWitness Witness) TrySignPath(ECPubKey[] allowedKeys, int requiredSignatures, ECPrivKey[] availableKeys, byte[] msg) { var allowedKeysSet = new HashSet<ECPubKey>(allowedKeys); - var result = new P2PKWitness(); + var signatures = new List<string>(); foreach (var privKey in availableKeys) { - if (result.Signatures.Length >= requiredSignatures) + if (signatures.Count >= requiredSignatures) break; var pubkey = privKey.CreatePubKey(); if (allowedKeysSet.Contains(pubkey)) { var sig = privKey.SignBIP340(msg); - result.Signatures = result.Signatures.Append(sig.ToHex()).ToArray(); + signatures.Add(sig.ToHex()); } } - return (result.Signatures.Length >= requiredSignatures, result); + return (signatures.Count >= requiredSignatures, new P2PKWitness { Signatures = signatures.ToArray() }); }The same pattern appears in
TrySignBlindPathat lines 204 and 214.
246-249: Add explicit non-null cast after filtering.The
Whereclause filters out nulls but doesn't change the inferred array type. While the values are guaranteed non-null at runtime, adding an explicit cast improves type safety and clarity.🔎 Proposed fix
var sigs = witness.Signatures .Select(s => SecpSchnorrSignature.TryCreate(Convert.FromHexString(s), out var sig) ? sig : null) .Where(signature => signature is not null) + .Cast<SecpSchnorrSignature>() .ToArray();
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
DotNut.Demo/Program.csDotNut.Tests/UnitTest1.csDotNut.slnDotNut/HTLCBuilder.csDotNut/HTLCProofSecret.csDotNut/HTLCWitness.csDotNut/JsonConverters/Nut10SecretJsonConverter.csDotNut/P2PKBuilder.csDotNut/P2PKProofSecret.csDotNut/SigAllHandler.cs
💤 Files with no reviewable changes (1)
- DotNut/JsonConverters/Nut10SecretJsonConverter.cs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
📚 Learning: 2025-11-29T15:42:55.222Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
Applied to files:
DotNut/HTLCBuilder.csDotNut/P2PKBuilder.csDotNut/SigAllHandler.csDotNut.Tests/UnitTest1.csDotNut/HTLCProofSecret.csDotNut/P2PKProofSecret.cs
🧬 Code graph analysis (4)
DotNut.Demo/Program.cs (1)
DotNut/P2PKBuilder.cs (2)
P2PKBuilder(6-171)P2PKBuilder(61-111)
DotNut/P2PKBuilder.cs (1)
DotNut/P2PKProofSecret.cs (1)
P2PKProofSecret(8-276)
DotNut/SigAllHandler.cs (3)
DotNut/Proof.cs (1)
Proof(7-31)DotNut/BlindedMessage.cs (1)
BlindedMessage(5-11)DotNut/Nut10ProofSecret.cs (1)
Nut10ProofSecret(5-18)
DotNut/P2PKProofSecret.cs (3)
DotNut/P2PKBuilder.cs (2)
P2PKBuilder(6-171)P2PKBuilder(61-111)DotNut/Cashu.cs (19)
ECPubKey(16-20)ECPubKey(22-26)ECPubKey(27-42)ECPubKey(51-54)ECPubKey(61-64)ECPubKey(66-69)ECPubKey(71-75)ECPubKey(77-81)ECPubKey(128-132)ECPrivKey(56-59)ECPrivKey(84-95)ECPrivKey(145-156)ToBytes(180-185)ToHex(175-178)ToHex(193-196)ToHex(197-200)ToHex(201-204)Cashu(8-205)ComputeZx(134-143)DotNut/P2PKWitness.cs (1)
P2PKWitness(5-8)
🪛 Gitleaks (8.30.0)
DotNut/SigAllHandler.cs
[high] 219-219: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (17)
DotNut.sln (1)
11-12: Consider removing or renaming the ConsoleApp1 project.The generic name "ConsoleApp1" suggests this may be a temporary development/debugging project. If it's intended for permanent inclusion, consider giving it a more descriptive name. If it was added for local testing, it should likely be excluded from the PR.
DotNut.Demo/Program.cs (1)
478-483: LGTM!The type rename from
P2PkBuildertoP2PKBuilderis correctly applied. The demo code provides good examples of multisig and time-locked P2PK usage.DotNut.Tests/UnitTest1.cs (2)
358-386: Good test coverage for new P2PK rules.The test properly validates that after locktime expiry, proofs are spendable on both standard and refund paths per the new rules from cashubtc/nuts#315. The test verifies both paths with appropriate signatures.
388-477: Comprehensive SIG_ALL test coverage.Excellent test coverage for the new
SigAllHandlerfunctionality including:
- Basic swap request verification
- Multisig scenarios
- Refund locktime scenarios
- HTLC with preimage
- Melt requests with quote ID
The tests validate both valid and invalid cases, ensuring robust verification logic.
DotNut/HTLCProofSecret.cs (2)
14-19: LGTM!The
GetAllowedPubkeysoverride properly delegates to the builder and returns the correct signature threshold.
21-37: LGTM!The refund path logic correctly implements the new behavior:
- Returns
requiredSignatures = 0with empty array when no refund keys exist (proof spendable without signature after locktime)- Uses
RefundSignatureThreshold ?? 1as the default threshold- Returns
nullforrequiredSignatureswhen lock hasn't expired (no refund condition)DotNut/HTLCWitness.cs (1)
7-10: LGTM!Making
Preimagenullable aligns with the new P2PK rules where HTLC proofs can be spent on the refund path after locktime without requiring the preimage. TheJsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)ensures clean serialization.DotNut/P2PKBuilder.cs (3)
6-17: LGTM!The class rename to
P2PKBuilderfollows conventional casing for acronyms. The newRefundSignatureThresholdproperty cleanly extends the builder for multi-sig refund scenarios.
40-45: LGTM!The logic correctly gates the
n_sigs_refundtag:
- Only adds when
RefundSignatureThresholdis set- Validates that enough refund keys exist (
refundKeys.Length >= refundSignatureThreshold)- Only applies within the
Lock.HasValueblock, since refund conditions are only relevant with timelocks
86-92: LGTM!The loading of
n_sigs_refundcorrectly parses the tag value and setsRefundSignatureThreshold. The pattern is consistent with how other tags liken_sigsare loaded.DotNut/SigAllHandler.cs (2)
197-221: LGTM - Static analysis false positive.The Gitleaks warning about "Generic API Key" at line 219 is a false positive. The variable
secretis a local variable representing a proof secret, not an API key. The naming is appropriate for this domain.
223-229: LGTM!The
CheckIfEqualToFirsthelper correctly validates that all proofs share identicalDataandTags, which is required for SIG_ALL signing where all inputs must have matching conditions.DotNut/P2PKProofSecret.cs (5)
12-19: LGTM!The naming convention fix from
P2PkBuildertoP2PKBuilderis consistent with the updated class name. TheGetAllowedPubkeysmethod correctly delegates to the builder.
21-37: LGTM!The refund path logic correctly handles three scenarios:
- Timelock expired with no refund keys → spendable without signature
- Timelock expired with refund keys → requires threshold signatures
- Timelock not expired or no lock → refund path unavailable
111-121: Verify that signature verification logic is intentional for multi-sig.The current implementation counts valid signature-key pairs but doesn't enforce that:
- Each signature is only verified against one key
- Each key is only used to verify one signature
If duplicate signatures are provided, or if a signature matches multiple keys, the count could be inflated.
Consider whether the protocol requires 1:1 matching between signatures and keys:
private bool VerifyPath(ECPubKey[] allowedKeys, int requiredSignatures, SecpSchnorrSignature[] sigs, byte[] hash) { if (sigs.Length < requiredSignatures) return false; var xonlyKeys = allowedKeys.Select(k => k.ToXOnlyPubKey()).ToArray(); - var validCount = sigs.Count(s => xonlyKeys.Any(xonly => xonly.SigVerifyBIP340(s, hash))); - - return validCount >= requiredSignatures; + var usedKeys = new HashSet<int>(); + var validCount = 0; + + foreach (var sig in sigs) + { + for (int i = 0; i < xonlyKeys.Length; i++) + { + if (usedKeys.Contains(i)) continue; + if (xonlyKeys[i].SigVerifyBIP340(sig, hash)) + { + usedKeys.Add(i); + validCount++; + break; + } + } + } + + return validCount >= requiredSignatures; }
178-222: LGTM!The blinded path signing logic correctly handles both the positive and negated key paths, which is necessary for BIP340 x-only pubkey compatibility. The slot tracking prevents key reuse.
Note: The same
Append().ToArray()allocation pattern at lines 204 and 214 could be optimized as mentioned earlier.
253-268: LGTM!The verification logic correctly mirrors the signing logic by:
- Checking if no signature is required (returns true)
- Trying normal path verification first
- Falling back to refund path if available and normal path fails
Fix/new p2pk htlc rules
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
DotNut/P2PKProofSecret.cs (2)
73-73: Typo: "notmal" → "normal".This typo has already been flagged in a previous review.
164-165: Inconsistency resolved.A previous review flagged this line as inconsistent with line 70, noting that
GenerateBlindWitnessreturnednew P2PKWitness()whileGenerateWitnessreturnednull. The current code shows both methods now returnnullwhenrequiredRefundSignatures == 0, making them consistent.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
DotNut/P2PKProofSecret.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-29T15:42:55.222Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
Applied to files:
DotNut/P2PKProofSecret.cs
🔇 Additional comments (4)
DotNut/P2PKProofSecret.cs (4)
45-91: Approve: Witness generation with refund path fallback.The nullable return type and dual-path logic (normal → refund fallback) correctly implement the intended semantics. Early return when
requiredRefundSignatures == 0is appropriate (no witness required when refund has no signature threshold). The exception on line 90 ensures callers are notified when neither path can produce sufficient signatures.
93-125: Approve: Well-structured signing and verification helpers.
TrySignPathefficiently matches available keys against allowed keys using a HashSet and accumulates signatures up to the required threshold.VerifyPathcorrectly counts valid signatures and enforces the threshold. Both methods are clear and correct.
135-183: Approve: Blind witness generation mirrors standard path.The nullable return types and dual-path fallback logic (normal → refund) correctly mirror the non-blind
GenerateWitnessbehavior. The implementation is consistent and sound.
250-282: Approve: Dual-path witness verification.The verification logic correctly checks both normal and refund paths, mirroring the generation logic. The early return when
requiredRefundSignatures == 0appropriately treats the "no witness required" case as always valid. Defensive null filtering on line 256-257 prevents issues with malformed signatures.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
DotNut/P2PKProofSecret.cs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
📚 Learning: 2025-11-29T15:42:55.222Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
Applied to files:
DotNut/P2PKProofSecret.cs
🧬 Code graph analysis (1)
DotNut/P2PKProofSecret.cs (3)
DotNut/P2PKBuilder.cs (2)
P2PKBuilder(6-171)P2PKBuilder(61-111)DotNut/Cashu.cs (19)
ECPubKey(16-20)ECPubKey(22-26)ECPubKey(27-42)ECPubKey(51-54)ECPubKey(61-64)ECPubKey(66-69)ECPubKey(71-75)ECPubKey(77-81)ECPubKey(128-132)ECPrivKey(56-59)ECPrivKey(84-95)ECPrivKey(145-156)ToBytes(180-185)ToHex(175-178)ToHex(193-196)ToHex(197-200)ToHex(201-204)Cashu(8-205)ComputeZx(134-143)DotNut/P2PKWitness.cs (1)
P2PKWitness(5-8)
🔇 Additional comments (4)
DotNut/P2PKProofSecret.cs (4)
21-37: LGTM - Timelock logic correctly implemented.The refund pubkey retrieval logic correctly handles the timelock condition: when
Lock >= Now, the timelock hasn't expired yet, so refund keys are not accessible. The special case at lines 30-34 whererequiredSignatures = 0(freely spendable after lock expiry with no refund keys) is also handled appropriately.
45-91: Well-structured witness generation with proper path precedence.The witness generation flow correctly:
- Returns
nullwhen no witness is required (line 70)- Attempts the normal signing path first (lines 73-78)
- Falls back to the refund path when the timelock has expired and refund keys are available (lines 81-88)
- Throws a clear exception when neither path can produce sufficient signatures (line 90)
The nullable return type
P2PKWitness?appropriately signals to callers when witness generation is not necessary.
186-230: Good defensive programming with slot tracking.The
TrySignBlindPathmethod correctly uses aHashSet<int>(line 192) to track which key slots have been used, preventing the same slot from being signed multiple times (line 201, 210, 221). This ensures proper multi-signature semantics where each signature must correspond to a distinct key.This defensive approach should also be applied to the verification logic in
VerifyPathto prevent duplicate signature attacks.
250-282: Verification flow structure is sound, but relies on flawedVerifyPath.The overall verification logic correctly:
- Filters invalid signatures (line 256)
- Checks the normal path first (line 266)
- Falls back to the refund path when applicable (lines 270-274)
- Returns
truewhen no witness is required (lines 261-264)However, both verification branches call
VerifyPath, which has a critical vulnerability allowing duplicate signatures to bypass the multi-sig threshold. OnceVerifyPathis fixed, this method will work correctly.The security of this verification depends on fixing the duplicate signature issue in
VerifyPath(lines 115-125).
|
cashubtc/nuts#315 has now been merged, so this can be completed and merged when ready. |
Add Equals() to Nut10ProofSecret
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @DotNut/Nut10ProofSecret.cs:
- Line 54: Remove the unnecessary trailing semicolon after the closing brace in
the Nut10ProofSecret initializer/block (the lone "};" at the end of the
declaration); delete the semicolon so the block ends with just "}" to match C#
syntax for that construct and avoid a stray token.
🧹 Nitpick comments (5)
DotNut/Nut10ProofSecret.cs (1)
18-43: Consider implementingIEquatable<Nut10ProofSecret>.The equality implementation is correct and thorough. For completeness and to enable generic constraint optimizations (e.g., in
Dictionary<Nut10ProofSecret, T>), consider having the class implement theIEquatable<Nut10ProofSecret>interface explicitly:-public class Nut10ProofSecret +public class Nut10ProofSecret : IEquatable<Nut10ProofSecret>DotNut/P2PKBuilder.cs (1)
114-125: Use.Lengthfor arrays and differentiate error messages.
Pubkeys.Count()invokes LINQ; use.Lengthdirectly for arrays.- Both validation branches throw the same error message, making it harder to identify which threshold check failed.
♻️ Suggested fix
private void Validate() { - if (this.Pubkeys.Count() < SignatureThreshold) + if (this.Pubkeys.Length < SignatureThreshold) { - throw new ArgumentException("Signature threshold bigger than provided pubkeys count!"); + throw new ArgumentException("SignatureThreshold exceeds the number of Pubkeys."); } if(this.RefundSignatureThreshold is not null && (RefundPubkeys is null || RefundPubkeys.Length < RefundSignatureThreshold)) { - throw new ArgumentException("Signature threshold bigger than provided pubkeys count!"); + throw new ArgumentException("RefundSignatureThreshold exceeds the number of RefundPubkeys."); } }DotNut/SigAllHandler.cs (3)
10-14: Consider initializing collection properties or marking them nullable.
Proofs,PrivKeys, andBlindedMessagesare non-nullable reference types but lack default values. WhileTrySignnull-checks them, callers could inadvertently access uninitialized properties.♻️ Suggested fix
- public List<Proof> Proofs { get; set; } - public List<PrivKey> PrivKeys { get; set; } - public List<BlindedMessage> BlindedMessages { get; set; } + public List<Proof> Proofs { get; set; } = new(); + public List<PrivKey> PrivKeys { get; set; } = new(); + public List<BlindedMessage> BlindedMessages { get; set; } = new();
151-154: Consider catching specific exceptions or adding logging.Catching
Exceptionswallows all errors including unexpected ones (e.g.,NullReferenceException). For a verification method returningfalseon failure is acceptable, but catching only expected exceptions (likeArgumentException,JsonException) or adding debug logging would improve debuggability.
190-192: Empty catch block swallows all exceptions.This catch block silently swallows all exceptions, including unexpected ones. Consider catching only
JsonExceptionto avoid masking bugs.♻️ Suggested fix
- catch + catch (JsonException) { return false; }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
DotNut/Nut10ProofSecret.csDotNut/P2PKBuilder.csDotNut/SigAllHandler.cs
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
📚 Learning: 2025-11-08T01:49:27.299Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 21
File: DotNut/P2PkBuilder.cs:100-107
Timestamp: 2025-11-08T01:49:27.299Z
Learning: In the DotNut codebase, the PrivKey class (DotNut/PrivKey.cs) defines an implicit operator that converts PrivKey to ECPrivKey by returning privKey.Key. This allows PrivKey instances to be passed directly to methods expecting ECPrivKey parameters without explicit conversion.
Applied to files:
DotNut/Nut10ProofSecret.cs
📚 Learning: 2025-09-17T17:11:38.738Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 16
File: DotNut/PubKey.cs:41-51
Timestamp: 2025-09-17T17:11:38.738Z
Learning: NBitcoin's ECPubKey (from NBitcoin.Secp256k1) implements value-based equality comparison through operator ==, not reference comparison. The operator compares the underlying cryptographic key material, making it suitable for structural equality implementations.
Applied to files:
DotNut/Nut10ProofSecret.cs
📚 Learning: 2025-11-29T15:42:55.222Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
Applied to files:
DotNut/Nut10ProofSecret.csDotNut/SigAllHandler.csDotNut/P2PKBuilder.cs
📚 Learning: 2026-01-11T17:55:03.188Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs:1-32
Timestamp: 2026-01-11T17:55:03.188Z
Learning: In the DotNut codebase, PubKey and PrivKey are defined as internal types in the DotNut namespace (DotNut/PubKey.cs and DotNut/PrivKey.cs), not from NBitcoin.Secp256k1. For files in child namespaces (e.g., DotNut.Abstractions) that reference these types, add an explicit 'using DotNut;' directive at the top. This applies to all DotNut.* .cs files that reference these types; do not assume a global using will bring them in.
Applied to files:
DotNut/Nut10ProofSecret.csDotNut/SigAllHandler.csDotNut/P2PKBuilder.cs
🧬 Code graph analysis (3)
DotNut/Nut10ProofSecret.cs (2)
DotNut/KeysetId.cs (5)
Equals(10-17)Equals(24-27)Equals(29-33)GetHashCode(19-22)GetHashCode(35-38)DotNut/PubKey.cs (2)
Equals(41-46)GetHashCode(48-51)
DotNut/SigAllHandler.cs (3)
DotNut/Proof.cs (1)
Proof(7-31)DotNut/Nut10ProofSecret.cs (1)
Nut10ProofSecret(5-76)DotNut/P2PKBuilder.cs (5)
P2PKProofSecret(19-60)P2PKProofSecret(135-140)P2PKProofSecret(143-161)P2PKBuilder(6-185)P2PKBuilder(62-112)
DotNut/P2PKBuilder.cs (1)
DotNut/P2PKProofSecret.cs (1)
P2PKProofSecret(8-299)
🔇 Additional comments (8)
DotNut/Nut10ProofSecret.cs (1)
65-74: LGTM!The equality operators are correctly implemented, properly handling null comparisons and delegating to
Equals.DotNut/P2PKBuilder.cs (4)
6-6: LGTM on class rename.The rename from
P2PkBuildertoP2PKBuilderimproves consistency with standard acronym casing conventions.
17-21: LGTM!Adding
RefundSignatureThresholdand callingValidate()at the start ofBuild()is a solid defensive approach.
40-46: LGTM!The logic correctly defaults
RefundSignatureThresholdto 1 when refund pubkeys are present and only emits then_sigs_refundtag when the threshold exceeds 1, mirroring the existingn_sigspattern.
88-93: LGTM!The
n_sigs_refundtag parsing follows the established pattern for other tags.DotNut/SigAllHandler.cs (3)
19-82: LGTM!
TrySigncorrectly handles validation, message construction, and branches for HTLC (with preimage) vs P2PK paths, including blind witness generation whenP2PkEis present. Based on learnings, theHTLCProofSecret is P2PKProofSecretinheritance is properly leveraged here.
84-130: LGTM!The message construction correctly enforces SIG_ALL requirements: all proofs must be Nut10Secrets with identical data/tags. The new
Nut10ProofSecretequality operator is appropriately used here.
197-221: LGTM!The validation logic correctly handles HTLC and P2PK secret types via their respective builders. The fallback case (line 211) creates a builder with
SigFlag = null, which will intentionally fail theSIG_ALLcheck on line 214—a safe default for unrecognized secret types.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
DotNut/Nut10ProofSecret.cs (2)
5-5: Consider implementingIEquatable<Nut10ProofSecret>.When overriding
Equals(object)and providing a typedEquals(T)method, implementingIEquatable<T>is the recommended pattern. This enables generic collections and LINQ operations to use the typed equality method directly, avoiding boxing.♻️ Suggested implementation
-public class Nut10ProofSecret +public class Nut10ProofSecret : IEquatable<Nut10ProofSecret>Also applies to: 19-43
45-63: Hash computation flattens array structure, increasing collision likelihood.The current approach adds all tag strings sequentially without encoding array boundaries. For example,
[["a","b"],["c"]]and[["a"],["b","c"]]produce identical hashes. This is technically valid (collisions are allowed), but could impact performance in hash-based collections if such patterns are common.♻️ Optional: Add structural markers to reduce collisions
foreach (var tagArray in this.Tags) { + hash.Add(tagArray?.Length ?? -1); // Mark array boundary foreach (var tag in tagArray) { hash.Add(tag); } }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
DotNut/Nut10ProofSecret.cs
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
📚 Learning: 2025-11-08T01:49:27.299Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 21
File: DotNut/P2PkBuilder.cs:100-107
Timestamp: 2025-11-08T01:49:27.299Z
Learning: In the DotNut codebase, the PrivKey class (DotNut/PrivKey.cs) defines an implicit operator that converts PrivKey to ECPrivKey by returning privKey.Key. This allows PrivKey instances to be passed directly to methods expecting ECPrivKey parameters without explicit conversion.
Applied to files:
DotNut/Nut10ProofSecret.cs
📚 Learning: 2026-01-11T17:55:03.188Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/Abstractions/Interfaces/IMintQuoteBuilder.cs:1-32
Timestamp: 2026-01-11T17:55:03.188Z
Learning: In the DotNut codebase, PubKey and PrivKey are defined as internal types in the DotNut namespace (DotNut/PubKey.cs and DotNut/PrivKey.cs), not from NBitcoin.Secp256k1. For files in child namespaces (e.g., DotNut.Abstractions) that reference these types, add an explicit 'using DotNut;' directive at the top. This applies to all DotNut.* .cs files that reference these types; do not assume a global using will bring them in.
Applied to files:
DotNut/Nut10ProofSecret.cs
📚 Learning: 2025-09-17T17:11:38.738Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 16
File: DotNut/PubKey.cs:41-51
Timestamp: 2025-09-17T17:11:38.738Z
Learning: NBitcoin's ECPubKey (from NBitcoin.Secp256k1) implements value-based equality comparison through operator ==, not reference comparison. The operator compares the underlying cryptographic key material, making it suitable for structural equality implementations.
Applied to files:
DotNut/Nut10ProofSecret.cs
📚 Learning: 2025-11-29T15:42:55.222Z
Learnt from: d4rp4t
Repo: Kukks/DotNut PR: 23
File: DotNut/NUT11/SigAllHandler.cs:19-77
Timestamp: 2025-11-29T15:42:55.222Z
Learning: In DotNut, HTLCProofSecret inherits from P2PKProofSecret, forming a base-derived relationship where HTLC secrets are a specialized form of P2PK secrets. Type checks using `is P2PKProofSecret` will match both P2PK and HTLC secrets due to this inheritance.
Applied to files:
DotNut/Nut10ProofSecret.cs
🧬 Code graph analysis (1)
DotNut/Nut10ProofSecret.cs (2)
DotNut/KeysetId.cs (5)
Equals(10-17)Equals(24-27)Equals(29-33)GetHashCode(19-22)GetHashCode(35-38)DotNut/PubKey.cs (2)
Equals(41-46)GetHashCode(48-51)
🔇 Additional comments (1)
DotNut/Nut10ProofSecret.cs (1)
65-74: LGTM!The equality operators correctly handle null operands and follow the standard C# pattern.
| ((this.Tags == null && s.Tags == null) || | ||
| (this.Tags != null && s.Tags != null && this.Tags.Length == s.Tags.Length && | ||
| this.Tags.Zip(s.Tags).All(pair => pair.First.SequenceEqual(pair.Second)))); |
There was a problem hiding this comment.
Potential NullReferenceException if inner tag arrays are null.
If Tags contains null elements (e.g., new string[][] { new[] {"a"}, null }), both SequenceEqual in Equals and the inner foreach in GetHashCode will throw. If null inner arrays are not valid by design, consider adding a guard or documenting this constraint.
🛡️ Optional defensive fix for Equals
- this.Tags.Zip(s.Tags).All(pair => pair.First.SequenceEqual(pair.Second))));
+ this.Tags.Zip(s.Tags).All(pair =>
+ (pair.First == null && pair.Second == null) ||
+ (pair.First != null && pair.Second != null && pair.First.SequenceEqual(pair.Second)))));Also applies to: 55-60
cashubtc/nuts#302
cashubtc/nuts#315
Summary by CodeRabbit
New Features
API Changes
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.