-
Notifications
You must be signed in to change notification settings - Fork 184
feat: confidential transfers helper functions #1096
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
Draft
catmcgee
wants to merge
8
commits into
solana-program:main
Choose a base branch
from
catmcgee:main
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.
Draft
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4cc2255
interface: add auditor ciphertext fields to Transfer data
catmcgee 057125c
interface: split ConfigureAccount and ConfigureAccountWithRegistry
catmcgee 8d535af
interface: omit absent optional accounts in Withdraw and Transfer
catmcgee 12f5958
confidential transfer helpers using the generated builders
catmcgee 4ac8f0f
rename helpers to InstructionPlan suffix, return Instruction from app…
catmcgee 758e77b
drop toBigIntAmount, use getTupleEncoder for owner-mint seed
catmcgee 5d8f9bc
reject negative amounts and handle indices
catmcgee c739ee9
dissolve custom ZK client interface, use @solana/zk-sdk types directly
catmcgee 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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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,95 @@ | ||
| // We depend on @noble/curves because the Web Crypto API does not expose | ||
| // Ristretto255 point arithmetic, and `@solana/zk-sdk` (the WASM SDK) does | ||
| // not currently expose `ElGamalCiphertext` arithmetic (subtract / multiply | ||
| // by scalar / lo-hi combination) over its public API. Once the WASM SDK | ||
| // adds those methods, delete this file and call into the SDK directly. | ||
| import { ristretto255 } from '@noble/curves/ed25519'; | ||
| import { type ReadonlyUint8Array } from '@solana/kit'; | ||
|
|
||
| const { Point: RistrettoPoint } = /* @__PURE__ */ ristretto255; | ||
|
|
||
| function pointFromBytes(bytes: ReadonlyUint8Array) { | ||
| return RistrettoPoint.fromHex(new Uint8Array(bytes)); | ||
| } | ||
|
|
||
| function ciphertextToPoints(ciphertext: ReadonlyUint8Array) { | ||
| if (ciphertext.length !== 64) { | ||
| throw new Error(`Expected 64 ciphertext bytes, got ${ciphertext.length}.`); | ||
| } | ||
|
|
||
| return { | ||
| commitment: pointFromBytes(ciphertext.slice(0, 32)), | ||
| handle: pointFromBytes(ciphertext.slice(32, 64)), | ||
| }; | ||
| } | ||
|
|
||
| function pointsToCiphertext(commitment: ReturnType<typeof pointFromBytes>, handle: ReturnType<typeof pointFromBytes>) { | ||
| const ciphertext = new Uint8Array(64); | ||
| ciphertext.set(commitment.toRawBytes(), 0); | ||
| ciphertext.set(handle.toRawBytes(), 32); | ||
| return ciphertext; | ||
| } | ||
|
|
||
| /** | ||
| * Extracts a single ElGamal ciphertext (commitment + one handle) from a | ||
| * grouped ciphertext. The grouped layout is: 32-byte commitment followed | ||
| * by N 32-byte handles. The returned 64-byte array is [commitment, handle]. | ||
| */ | ||
| export function extractCiphertextFromGroupedBytes(groupedCiphertext: ReadonlyUint8Array, handleIndex: number) { | ||
| if (!Number.isInteger(handleIndex) || handleIndex < 0) { | ||
| throw new Error(`handleIndex must be a non-negative integer, got ${handleIndex}.`); | ||
| } | ||
| const start = 32 + handleIndex * 32; | ||
| const end = start + 32; | ||
| if (groupedCiphertext.length < end) { | ||
| throw new Error(`Grouped ciphertext does not contain handle ${handleIndex}.`); | ||
| } | ||
|
|
||
| const ciphertext = new Uint8Array(64); | ||
| ciphertext.set(groupedCiphertext.slice(0, 32), 0); | ||
| ciphertext.set(groupedCiphertext.slice(start, end), 32); | ||
| return ciphertext; | ||
| } | ||
|
|
||
| function subtractCiphertexts(left: ReadonlyUint8Array, right: ReadonlyUint8Array) { | ||
| const leftPoints = ciphertextToPoints(left); | ||
| const rightPoints = ciphertextToPoints(right); | ||
| return pointsToCiphertext( | ||
| leftPoints.commitment.subtract(rightPoints.commitment), | ||
| leftPoints.handle.subtract(rightPoints.handle), | ||
| ); | ||
| } | ||
|
|
||
| function combineLoHiCiphertexts(ciphertextLo: ReadonlyUint8Array, ciphertextHi: ReadonlyUint8Array, bitLength: bigint) { | ||
| const scale = 1n << bitLength; | ||
| const loPoints = ciphertextToPoints(ciphertextLo); | ||
| const hiPoints = ciphertextToPoints(ciphertextHi); | ||
| return pointsToCiphertext( | ||
| loPoints.commitment.add(hiPoints.commitment.multiply(scale)), | ||
| loPoints.handle.add(hiPoints.handle.multiply(scale)), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Combines lo/hi ciphertext halves (hi << bitLength + lo) and subtracts the | ||
| * result from `left`. Used to compute the new available-balance ciphertext | ||
| * after a confidential transfer. | ||
| */ | ||
| export function subtractWithLoHiCiphertexts( | ||
| left: ReadonlyUint8Array, | ||
| ciphertextLo: ReadonlyUint8Array, | ||
| ciphertextHi: ReadonlyUint8Array, | ||
| bitLength: bigint, | ||
| ) { | ||
| return subtractCiphertexts(left, combineLoHiCiphertexts(ciphertextLo, ciphertextHi, bitLength)); | ||
| } | ||
|
|
||
| /** | ||
| * Subtracts a plaintext amount from an ElGamal ciphertext by removing | ||
| * `amount * G` from the commitment. Used to compute the expected | ||
| * remaining-balance ciphertext after a confidential withdraw. | ||
| */ | ||
| export function subtractAmountFromCiphertext(ciphertext: ReadonlyUint8Array, amount: bigint) { | ||
| const { commitment, handle } = ciphertextToPoints(ciphertext); | ||
| return pointsToCiphertext(commitment.subtract(RistrettoPoint.BASE.multiply(amount)), handle); | ||
| } | ||
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.
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.
Maybe we should check for negative
handleIndex.