-
Notifications
You must be signed in to change notification settings - Fork 102
Feat/sp sending #496
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
Open
jvgelder
wants to merge
26
commits into
caravan-bitcoin:main
Choose a base branch
from
jvgelder:feat/sp-sending
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Feat/sp sending #496
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
81981ca
chore(psbt): Add silent payment fields
jvgelder dcfc2a9
chore(psbt): Add silent payment fields
jvgelder 8b2a8ab
chore(psbt): Add output functionality
jvgelder ed8b289
feat(psbt): Add BIP352 silent payment output script computation
jvgelder 296fa75
fix(psbt): Prevent creating breaking change with PSBT_OUT_SCRIPT
jvgelder 80e7c11
chore(psbtv2): Added test vectors from BIP375
jvgelder 8f0df4b
chore(psbtv2): Add validation
jvgelder 0e57be0
fix(psbtv2): correct silent payment output derivation
jvgelder 23cff68
chore(psbtv2): Replace checks with assert calls
jvgelder ea81977
chore(psbtv2): Proper label and k checking
jvgelder 1408cc2
fix(caravan-psbt): harden BIP375 silent payment validation
jvgelder 20b7395
feat(caravan-psbt): add BIP374 DLEQ proofs for silent payments
jvgelder 124ba9e
chore(dleq): Cleanup functions
jvgelder e1f7cfd
chore(bip375): Enable all tests
jvgelder 8ee5f65
fix(psbtv2): Fix nums check
jvgelder d1db68e
fix(caravan-psbt): require prevout-matched keys for SP derivation
jvgelder 8e662e4
refactor(caravan-psbt): add prevout-matched SP pubkey helpers
jvgelder d97620a
test(caravan-psbt): update BIP375 vector fixtures
jvgelder 80bfccc
fix(caravan-psbt): enforce prevout-matched silent payment input keys
jvgelder 98ce2c3
refactor(caravan-psbt): expose NUMS script-path detection for validation
jvgelder a391dd2
fix(caravan-psbt): require prevout-matched keys for SP derivation
jvgelder af82d43
chore(pstv2): Update vectors
jvgelder 6fc4630
chore(psbtv2): Cleanup redundant code
jvgelder 0275c92
chore(psbtv2): Revert PSBT_OUT_SCRIPT changes
jvgelder 5a12230
chore(psbtv2): Improve missing warning
jvgelder e1a6b39
chore(psbtv2): Cleanup and remove dead code
jvgelder File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| --- | ||
| "@caravan/psbt": minor | ||
| --- | ||
|
|
||
| Add BIP375 silent payment sending support to PsbtV2 | ||
|
|
||
| - Add PSBT_OUT_SP_V0_INFO and PSBT_OUT_SP_V0_LABEL getters and methods | ||
| - Add PSBT_GLOBAL_SP_ECDH_SHARE, PSBT_GLOBAL_SP_DLEQ getters and methods | ||
| - Add PSBT_IN_SP_ECDH_SHARE, PSBT_IN_SP_DLEQ getters and methods | ||
| - Add silentPayment option to addOutput() for SP output construction | ||
| - Add computeSilentPaymentOutputScripts() implementing BIP352 derivation | ||
| - Add hasSilentPaymentOutputs, hasAllSPOutputScripts, hasCompleteECDHCoverage predicates | ||
| - Add eligibleIndices, sumECDHShares, computeInputHash, deriveSilentPaymentOutput to silentpayment.ts | ||
| - Update isReadyForSigner to enforce BIP375 signing rules | ||
| - Update getTransactionId() for BIP375 unique identification |
2,074 changes: 2,074 additions & 0 deletions
2,074
packages/caravan-psbt/src/fixtures/bip375_vectors.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,208 @@ | ||
| import { describe, expect, it } from "vitest"; | ||
| import { secp256k1 } from "@noble/curves/secp256k1"; | ||
|
|
||
| import { | ||
| generateDLEQProof, | ||
| multiplyCompressedPoint, | ||
| verifyDLEQProof, | ||
| } from "./dleq"; | ||
| function hex(s: string): Buffer { | ||
| return Buffer.from(s, "hex"); | ||
| } | ||
|
|
||
| function pubkeyFromSecret(secret: Buffer): Buffer { | ||
| return Buffer.from( | ||
| secp256k1.ProjectivePoint.BASE.multiply( | ||
| BigInt(`0x${secret.toString("hex")}`), | ||
| ).toRawBytes(true), | ||
| ); | ||
| } | ||
|
|
||
| describe("BIP374 DLEQ", () => { | ||
| it("generates a proof that verifies", () => { | ||
| const secret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000001", | ||
| ); | ||
| const baseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000002", | ||
| ); | ||
| const auxRand = Buffer.alloc(32, 0); | ||
|
|
||
| const publicKey = pubkeyFromSecret(secret); | ||
| const basePoint = pubkeyFromSecret(baseSecret); | ||
| const result = multiplyCompressedPoint(basePoint, secret); | ||
|
|
||
| const proof = generateDLEQProof({ | ||
| secret, | ||
| basePoint, | ||
| auxRand, | ||
| }); | ||
|
|
||
| expect(proof.length).toBe(64); | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof, | ||
| publicKey, | ||
| basePoint, | ||
| result, | ||
| }), | ||
| ).toBe(true); | ||
| }); | ||
|
|
||
| it("fails verification with the wrong public key", () => { | ||
| const secret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000001", | ||
| ); | ||
| const wrongSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000003", | ||
| ); | ||
| const baseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000002", | ||
| ); | ||
|
|
||
| const basePoint = pubkeyFromSecret(baseSecret); | ||
| const result = multiplyCompressedPoint(basePoint, secret); | ||
| const wrongPublicKey = pubkeyFromSecret(wrongSecret); | ||
|
|
||
| const proof = generateDLEQProof({ | ||
| secret, | ||
| basePoint, | ||
| auxRand: Buffer.alloc(32, 0), | ||
| }); | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof, | ||
| publicKey: wrongPublicKey, | ||
| basePoint, | ||
| result, | ||
| }), | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it("fails verification with the wrong base point", () => { | ||
| const secret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000001", | ||
| ); | ||
| const baseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000002", | ||
| ); | ||
| const wrongBaseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000003", | ||
| ); | ||
|
|
||
| const publicKey = pubkeyFromSecret(secret); | ||
| const basePoint = pubkeyFromSecret(baseSecret); | ||
| const wrongBasePoint = pubkeyFromSecret(wrongBaseSecret); | ||
| const result = multiplyCompressedPoint(basePoint, secret); | ||
|
|
||
| const proof = generateDLEQProof({ | ||
| secret, | ||
| basePoint, | ||
| auxRand: Buffer.alloc(32, 0), | ||
| }); | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof, | ||
| publicKey, | ||
| basePoint: wrongBasePoint, | ||
| result, | ||
| }), | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it("fails verification with the wrong ECDH result", () => { | ||
| const secret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000001", | ||
| ); | ||
| const baseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000002", | ||
| ); | ||
| const wrongSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000003", | ||
| ); | ||
|
|
||
| const publicKey = pubkeyFromSecret(secret); | ||
| const basePoint = pubkeyFromSecret(baseSecret); | ||
| const wrongResult = multiplyCompressedPoint(basePoint, wrongSecret); | ||
|
|
||
| const proof = generateDLEQProof({ | ||
| secret, | ||
| basePoint, | ||
| auxRand: Buffer.alloc(32, 0), | ||
| }); | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof, | ||
| publicKey, | ||
| basePoint, | ||
| result: wrongResult, | ||
| }), | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it("fails verification when proof bytes are mutated", () => { | ||
| const secret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000001", | ||
| ); | ||
| const baseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000002", | ||
| ); | ||
|
|
||
| const publicKey = pubkeyFromSecret(secret); | ||
| const basePoint = pubkeyFromSecret(baseSecret); | ||
| const result = multiplyCompressedPoint(basePoint, secret); | ||
|
|
||
| const proof = generateDLEQProof({ | ||
| secret, | ||
| basePoint, | ||
| auxRand: Buffer.alloc(32, 0), | ||
| }); | ||
|
|
||
| const mutated = Buffer.from(proof); | ||
| mutated[0] ^= 1; | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof: mutated, | ||
| publicKey, | ||
| basePoint, | ||
| result, | ||
| }), | ||
| ).toBe(false); | ||
| }); | ||
|
|
||
| it("rejects malformed proof lengths", () => { | ||
| const secret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000001", | ||
| ); | ||
| const baseSecret = hex( | ||
| "0000000000000000000000000000000000000000000000000000000000000002", | ||
| ); | ||
|
|
||
| const publicKey = pubkeyFromSecret(secret); | ||
| const basePoint = pubkeyFromSecret(baseSecret); | ||
| const result = multiplyCompressedPoint(basePoint, secret); | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof: Buffer.alloc(63), | ||
| publicKey, | ||
| basePoint, | ||
| result, | ||
| }), | ||
| ).toBe(false); | ||
|
|
||
| expect( | ||
| verifyDLEQProof({ | ||
| proof: Buffer.alloc(65), | ||
| publicKey, | ||
| basePoint, | ||
| result, | ||
| }), | ||
| ).toBe(false); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.