Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"packages/ripple-keypairs",
"packages/ripple-address-codec",
"packages/isomorphic",
"packages/secret-numbers"
"packages/secret-numbers",
"packages/mpt-crypto"
],
"npmClient": "npm",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
Expand Down
20 changes: 20 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

63 changes: 63 additions & 0 deletions packages/mpt-crypto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# @xrplf/mpt-crypto

Cryptographic primitives for **Confidential MPT (XLS-0096)** on the XRP Ledger,
exposed as a small **hex-in / hex-out** TypeScript API over a vendored
WebAssembly build of the reference C crypto library.

This package is an **optional peer dependency** of `xrpl`. Most `xrpl` users
never need it — it is only required (and lazily loaded) by the
[`xrpl/confidential`](../xrpl/src/confidential) builders, which assemble
Confidential MPT transactions. You typically interact with those builders rather
than calling this package directly.

## What it provides

Confidential MPT replaces a public MPT balance with **EC-ElGamal ciphertexts**
on-ledger and uses **zero-knowledge proofs** so validators can verify transfers
(no overdraft, amounts conserved) without seeing the amounts. This package
exposes the building blocks:

- **Encryption** — `encryptAmount`, `decryptAmount` (EC-ElGamal over secp256k1).
- **Commitments** — `getPedersenCommitment`, `generateBlindingFactor`.
- **Context hashes** — `getConvertContextHash`, `getConvertBackContextHash`,
`getSendContextHash`, `getClawbackContextHash`. Each binds a proof to a
specific transaction (account, issuance, sequence, …).
- **Proofs** — `getConvertProof`, `getConvertBackProof`,
`getConfidentialSendProof`, `getClawbackProof`.
- **Constants** — the fixed byte sizes (`PUBKEY_SIZE`, `ELGAMAL_TOTAL_SIZE`, the
per-transaction proof sizes, …) and the `bytesToHex` / `hexToBytes` helpers.

## Conventions

- Every byte argument and return value is an **uppercase, even-length hex
string** with no `0x` prefix (matching the rest of `xrpl.js`).
- Integer amounts are **`bigint`**, to losslessly carry the full `uint64_t`
range.
- Keys are a **secp256k1 keypair** (32-byte private key, 33-byte compressed
public key) — the same curve as a secp256k1 signing key, but a distinct key
used only for encryption. Generate one with `ripple-keypairs`
(`deriveKeypair(generateSeed({ algorithm: 'ecdsa-secp256k1' }))`).
- The WASM module is loaded once and cached on first use, so depending on this
package costs nothing until a confidential operation is actually invoked.

## Usage

```ts
import {
encryptAmount,
decryptAmount,
generateBlindingFactor,
} from '@xrplf/mpt-crypto'

const blinding = await generateBlindingFactor()
const ciphertext = await encryptAmount(1000n, publicKey, blinding)
const amount = await decryptAmount(ciphertext, privateKey) // 1000n
```

## The vendored WASM

`wasm/mpt_crypto.{js,wasm}` is a committed Emscripten build of the reference
`mpt-crypto` C library. **It must stay in lockstep with the `mpt-crypto` version
that `rippled` pins** — a mismatch produces valid-looking transactions that
`rippled` rejects with `tecBAD_PROOF`. When updating, rebuild from the same
`mpt-crypto` tag rippled uses and re-vendor both files.
66 changes: 66 additions & 0 deletions packages/mpt-crypto/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const globals = require('globals')
const eslintConfig = require('@xrplf/eslint-config/base').default
const tseslint = require('typescript-eslint')

module.exports = [
{
ignores: [
'**/node_modules/',
'**/dist/',
'coverage/',
'.nyc_output/',
'nyc.config.js',
'.idea/',
'**/*.js',
'wasm/',
'examples/',
],
},
...eslintConfig,
{
languageOptions: {
sourceType: 'module', // Allow the use of imports / ES modules
ecmaVersion: 2020,
parser: tseslint.parser, // Make ESLint compatible with TypeScript
parserOptions: {
// Enable linting rules with type information from our tsconfig
tsconfigRootDir: __dirname,
project: ['./tsconfig.eslint.json'],
ecmaFeatures: {
impliedStrict: true, // Enable global strict mode
},
},

globals: {
...globals.browser,
...globals.node,
...globals.es2020,
},
},

rules: {
// This creates a lot of false positives. We should turn this off in our
// general config.
'jsdoc/require-description-complete-sentence': 'off',

// ** TODO **
// all of the below are turned off for now during the migration to a
// monorepo. They need to actually be addressed!
// **
'@typescript-eslint/no-magic-numbers': 'off',
'jsdoc/require-returns': 'off',
'jsdoc/check-param-names': 'off',
'jsdoc/require-throws': 'off',
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param': 'off',
'jsdoc/check-examples': 'off', // Not implemented in eslint 8
'tsdoc/syntax': 'off',
'@typescript-eslint/no-require-imports': 'off',

// Disabled to match the other workspace packages: under flat config this
// rule errors without an .eslintrc, and every export here is consumed by
// external packages (which the rule cannot see).
'import/no-unused-modules': 'off',
},
},
]
36 changes: 36 additions & 0 deletions packages/mpt-crypto/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@xrplf/mpt-crypto",
"version": "0.1.0",
"private": true,
"description": "Confidential MPT (XLS-0096) cryptographic primitives for the XRP Ledger, compiled to WebAssembly",
"files": [
"dist/*",
"wasm/*",
"src/*"
],
Comment on lines +6 to +10

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for the mpt_crypto.wasm binary in the package

fd -t f 'mpt_crypto.wasm' packages/mpt-crypto/

Repository: XRPLF/xrpl.js

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== WASM files =="
ls -la packages/mpt-crypto/wasm || true
echo

echo "== References to mpt_crypto.wasm =="
rg -n "mpt_crypto\.wasm" -S packages/mpt-crypto || true
echo

echo "== WASM provenance/build docs (best-effort search) =="
rg -n "(build|builds|download|provenance|source|compile|generated|toolchain|wasm)" -S packages/mpt-crypto || true

Repository: XRPLF/xrpl.js

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== packages/mpt-crypto/src/module.ts around locateFile / wasm comment =="
sed -n '80,140p' packages/mpt-crypto/src/module.ts

echo
echo "== Search for any explicit build/download provenance keywords =="
rg -n "(vendored|download|build|compile|emcc|emscripten|source|provenance|generated)" packages/mpt-crypto || true

echo
echo "== Files array in packages/mpt-crypto/package.json =="
cat packages/mpt-crypto/package.json

echo
echo "== Any README/docs mentioning wasm =="
fd -t f -e md -e txt packages/mpt-crypto | xargs -r rg -n "wasm|mpt_crypto\.wasm" || true

Repository: XRPLF/xrpl.js

Length of output: 50769


Document the provenance of mpt_crypto.wasm (binary is present and packaged)

  • packages/mpt-crypto/wasm/mpt_crypto.wasm exists and is covered by packages/mpt-crypto/package.json’s "files": ["wasm/*", ...].
  • packages/mpt-crypto/src/module.ts notes the Emscripten glue locates mpt_crypto.wasm next to mpt_crypto.js, but it doesn’t explain how the WASM was built/downloaded.
  • Add a short README/comment describing the WASM’s provenance (build command/tooling and source it comes from, or download process/checksums).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/mpt-crypto/package.json` around lines 6 - 10, Add provenance
documentation for the packaged WASM by creating or updating a short README or
inline comment that explains how packages/mpt-crypto/wasm/mpt_crypto.wasm was
produced or obtained: state the upstream source/repo and commit/tag, the exact
build command/tooling (e.g., Emscripten build command and environment), or the
download URL and version, and include integrity details such as a SHA256
checksum and any verification steps; ensure this note is referenced from
packages/mpt-crypto/src/module.ts (where the Emscripten glue expects
mpt_crypto.wasm) and that packages/mpt-crypto/package.json's "files" list
remains accurate.

"main": "dist/index.js",
"types": "dist/index.d.ts",
"license": "ISC",
"keywords": [
"ripple",
"xrp",
"xrp ledger",
"xrpl",
"mpt",
"confidential"
],
"repository": {
"type": "git",
"url": "git@github.com:XRPLF/xrpl.js.git"
},
"scripts": {
"build": "tsc --build tsconfig.build.json",
"test": "jest --passWithNoTests --verbose false --silent=false ./test/*.test.ts",
"lint": "eslint . --max-warnings 0",
"clean": "rm -rf ./dist ./coverage tsconfig.build.tsbuildinfo"
},
"prettier": "@xrplf/prettier-config",
"engines": {
"node": ">= 18"
}
}
52 changes: 52 additions & 0 deletions packages/mpt-crypto/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Byte sizes mirroring `mpt-crypto/include/mpt_protocol.h`. These are the
* authoritative wire/buffer sizes for the Confidential MPT (XLS-0096) crypto
* primitives compiled into the vendored WASM module.
*/

/** secp256k1 compressed public / ElGamal key. */
export const PUBKEY_SIZE = 33
/** secp256k1 private key. */
export const PRIVKEY_SIZE = 32
/** ElGamal randomness / Pedersen blinding factor scalar. */
export const BLINDING_FACTOR_SIZE = 32
/** A full ElGamal ciphertext (C1 || C2). */
export const ELGAMAL_TOTAL_SIZE = 66
/** A Pedersen commitment point. */
export const PEDERSEN_COMMIT_SIZE = 33
/** The 32-byte transaction context hash (challenge) consumed by the proofs. */
export const CONTEXT_HASH_SIZE = 32
/** 20-byte XRPL AccountID. */
export const ACCOUNT_ID_SIZE = 20
/** 24-byte MPTokenIssuanceID. */
export const ISSUANCE_ID_SIZE = 24

/** ConfidentialMPTConvert ZKProof length. */
export const CONVERT_PROOF_SIZE = 64
/** ConfidentialMPTClawback ZKProof length. */
export const CLAWBACK_PROOF_SIZE = 64
/** ConfidentialMPTConvertBack ZKProof length (128 sigma + 688 bulletproof). */
export const CONVERT_BACK_PROOF_SIZE = 816
/** ConfidentialMPTSend ZKProof length (192 sigma + 754 bulletproof). */
export const SEND_PROOF_SIZE = 946

/**
* In-memory layout of the C `mpt_confidential_participant` struct
* (`{ uint8_t pubkey[33]; uint8_t ciphertext[66]; }`, alignment 1).
*/
export const PARTICIPANT_PUBKEY_OFFSET = 0
export const PARTICIPANT_CIPHERTEXT_OFFSET = PUBKEY_SIZE
export const PARTICIPANT_STRUCT_SIZE = PUBKEY_SIZE + ELGAMAL_TOTAL_SIZE

/**
* In-memory layout of the C `mpt_pedersen_proof_params` struct:
* `{ uint8_t pedersen_commitment[33]; uint64_t amount; uint8_t ciphertext[66];
* uint8_t blinding_factor[32]; }`. The `uint64_t` forces 8-byte alignment,
* so the commitment is padded from 33 to 40 and the struct size is rounded up
* to a multiple of 8.
*/
export const PEDERSEN_PARAMS_COMMITMENT_OFFSET = 0
export const PEDERSEN_PARAMS_AMOUNT_OFFSET = 40
export const PEDERSEN_PARAMS_CIPHERTEXT_OFFSET = 48
export const PEDERSEN_PARAMS_BLINDING_OFFSET = 114
export const PEDERSEN_PARAMS_STRUCT_SIZE = 152
Loading
Loading