diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8eb6800 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,152 @@ +# PrivacyLayer API Documentation + +This directory contains comprehensive API documentation for the PrivacyLayer privacy pool smart contract. + +## Files + +- **`openapi.yaml`** - OpenAPI 3.0 specification for all contract methods +- **`postman_collection.json`** - Postman collection for testing the API +- **`examples/`** - Code examples in multiple languages + +## OpenAPI Specification + +The `openapi.yaml` file documents all public methods of the PrivacyPool Soroban contract: + +### Initialization +- `POST /initialize` - Initialize the privacy pool + +### Core Operations +- `POST /deposit` - Deposit into the shielded pool +- `POST /withdraw` - Withdraw using a ZK proof + +### View Functions +- `GET /get_root` - Get current Merkle root +- `GET /deposit_count` - Get total deposits +- `GET /is_known_root` - Check if root is valid +- `GET /is_spent` - Check if nullifier is spent +- `GET /get_config` - Get pool configuration + +### Admin Functions +- `POST /pause` - Pause the pool (admin only) +- `POST /unpause` - Unpause the pool (admin only) +- `POST /set_verifying_key` - Update verifying key (admin only) + +## Viewing the Documentation + +### Option 1: Swagger UI (Recommended) + +Visit [Swagger Editor](https://editor.swagger.io/) and paste the contents of `openapi.yaml`. + +### Option 2: Redoc + +```bash +npm install -g redoc-cli +redoc-cli serve openapi.yaml +``` + +Then open http://localhost:8080 + +### Option 3: Local Swagger UI + +```bash +docker run -p 8080:8080 -e SWAGGER_JSON=/docs/openapi.yaml -v $(pwd):/docs swaggerapi/swagger-ui +``` + +Then open http://localhost:8080 + +## Postman Collection + +Import `postman_collection.json` into Postman to test the API endpoints. + +### Setup + +1. Open Postman +2. Click **Import** → **Upload Files** +3. Select `postman_collection.json` +4. Configure environment variables: + - `CONTRACT_ADDRESS` - Your deployed contract address + - `ADMIN_ADDRESS` - Admin Stellar address + - `USER_ADDRESS` - User Stellar address + +## Code Examples + +See the `examples/` directory for integration examples: + +- **TypeScript/JavaScript** - Using Stellar SDK +- **Python** - Using stellar-sdk +- **Rust** - Using soroban-sdk +- **cURL** - Raw HTTP requests + +## Contract ABI + +The contract ABI is automatically generated from the Rust source code: + +```bash +cd contracts/privacy_pool +cargo build --target wasm32-unknown-unknown --release +stellar contract bindings typescript --wasm target/wasm32-unknown-unknown/release/privacy_pool.wasm --output-dir ../../sdk/src/bindings +``` + +## Interactive API Testing + +### Using Stellar CLI + +```bash +# Get current root +stellar contract invoke \ + --id CONTRACT_ID \ + --source-account ACCOUNT \ + --network testnet \ + -- get_root + +# Check deposit count +stellar contract invoke \ + --id CONTRACT_ID \ + --source-account ACCOUNT \ + --network testnet \ + -- deposit_count +``` + +## API Versioning + +The API follows semantic versioning: +- **Major version** - Breaking changes to contract interface +- **Minor version** - New features, backward compatible +- **Patch version** - Bug fixes + +Current version: **1.0.0** + +## Security Considerations + +⚠️ **IMPORTANT**: This contract is unaudited. Do not use in production. + +### Authentication + +All state-changing operations require: +1. Valid Stellar transaction signature +2. Sufficient XLM for transaction fees +3. Token approval for deposit operations + +### Rate Limiting + +Consider implementing rate limiting at the relayer level to prevent: +- Spam deposits +- DoS attacks on the Merkle tree +- Excessive gas consumption + +### Privacy Notes + +- Fixed denominations prevent amount-based correlation +- Merkle tree depth of 20 supports up to 1,048,576 deposits +- Historical root buffer prevents front-running attacks +- Nullifier tracking prevents double-spending + +## Support + +- **Issues**: https://github.com/ANAVHEOBA/PrivacyLayer/issues +- **Discussions**: https://github.com/ANAVHEOBA/PrivacyLayer/discussions +- **Documentation**: https://github.com/ANAVHEOBA/PrivacyLayer/tree/main/docs + +## License + +MIT - see [LICENSE](../LICENSE) diff --git a/docs/examples/typescript-example.ts b/docs/examples/typescript-example.ts new file mode 100644 index 0000000..a3b2ed4 --- /dev/null +++ b/docs/examples/typescript-example.ts @@ -0,0 +1,277 @@ +// PrivacyLayer TypeScript/JavaScript Examples +// Using Stellar SDK and Soroban Client + +import * as StellarSdk from '@stellar/stellar-sdk'; +import { Contract, SorobanRpc } from '@stellar/stellar-sdk'; + +// Configuration +const CONTRACT_ADDRESS = 'CCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; +const NETWORK_PASSPHRASE = StellarSdk.Networks.TESTNET; +const RPC_URL = 'https://soroban-testnet.stellar.org'; + +// Initialize Soroban RPC client +const server = new SorobanRpc.Server(RPC_URL); + +// Initialize contract +const contract = new Contract(CONTRACT_ADDRESS); + +/** + * Example 1: Get current Merkle root + */ +async function getCurrentRoot() { + try { + const result = await server.getContractData( + CONTRACT_ADDRESS, + StellarSdk.xdr.ScVal.scvLedgerKeyContractInstance() + ); + + // Call get_root method + const tx = new StellarSdk.TransactionBuilder(account, { + fee: StellarSdk.BASE_FEE, + networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation(contract.call('get_root')) + .setTimeout(30) + .build(); + + const response = await server.sendTransaction(tx); + console.log('Current root:', response.result); + return response.result; + } catch (error) { + console.error('Error getting root:', error); + throw error; + } +} + +/** + * Example 2: Deposit into the pool + */ +async function deposit(sourceKeypair: StellarSdk.Keypair, commitment: string) { + try { + const sourceAccount = await server.getAccount(sourceKeypair.publicKey()); + + // Convert commitment to ScVal + const commitmentBytes = Buffer.from(commitment.replace('0x', ''), 'hex'); + const commitmentScVal = StellarSdk.xdr.ScVal.scvBytes(commitmentBytes); + + // Build transaction + const tx = new StellarSdk.TransactionBuilder(sourceAccount, { + fee: '10000', + networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation( + contract.call( + 'deposit', + StellarSdk.Address.fromString(sourceKeypair.publicKey()).toScVal(), + commitmentScVal + ) + ) + .setTimeout(30) + .build(); + + // Sign transaction + tx.sign(sourceKeypair); + + // Submit transaction + const response = await server.sendTransaction(tx); + console.log('Deposit response:', response); + + // Wait for confirmation + let status = await server.getTransaction(response.hash); + while (status.status === 'PENDING' || status.status === 'NOT_FOUND') { + await new Promise(resolve => setTimeout(resolve, 1000)); + status = await server.getTransaction(response.hash); + } + + if (status.status === 'SUCCESS') { + console.log('Deposit successful!'); + console.log('Leaf index:', status.returnValue); + return status.returnValue; + } else { + throw new Error(`Transaction failed: ${status.status}`); + } + } catch (error) { + console.error('Error depositing:', error); + throw error; + } +} + +/** + * Example 3: Generate commitment (off-chain) + */ +function generateCommitment(nullifier: Buffer, secret: Buffer): string { + // In production, use Poseidon2 hash + // This is a placeholder - actual implementation requires Poseidon2 library + const crypto = require('crypto'); + const hash = crypto.createHash('sha256'); + hash.update(Buffer.concat([nullifier, secret])); + return '0x' + hash.digest('hex'); +} + +/** + * Example 4: Withdraw from the pool + */ +async function withdraw( + sourceKeypair: StellarSdk.Keypair, + proof: { a: string; b: string; c: string }, + publicInputs: { + root: string; + nullifier_hash: string; + recipient: string; + amount: string; + relayer: string; + fee: string; + } +) { + try { + const sourceAccount = await server.getAccount(sourceKeypair.publicKey()); + + // Convert proof to ScVal + const proofScVal = StellarSdk.xdr.ScVal.scvMap([ + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('a'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(proof.a.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('b'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(proof.b.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('c'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(proof.c.replace('0x', ''), 'hex')), + }), + ]); + + // Convert public inputs to ScVal + const publicInputsScVal = StellarSdk.xdr.ScVal.scvMap([ + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('root'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(publicInputs.root.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('nullifier_hash'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(publicInputs.nullifier_hash.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('recipient'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(publicInputs.recipient.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('amount'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(publicInputs.amount.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('relayer'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(publicInputs.relayer.replace('0x', ''), 'hex')), + }), + new StellarSdk.xdr.ScMapEntry({ + key: StellarSdk.xdr.ScVal.scvSymbol('fee'), + val: StellarSdk.xdr.ScVal.scvBytes(Buffer.from(publicInputs.fee.replace('0x', ''), 'hex')), + }), + ]); + + // Build transaction + const tx = new StellarSdk.TransactionBuilder(sourceAccount, { + fee: '100000', + networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation( + contract.call('withdraw', proofScVal, publicInputsScVal) + ) + .setTimeout(30) + .build(); + + // Sign transaction + tx.sign(sourceKeypair); + + // Submit transaction + const response = await server.sendTransaction(tx); + console.log('Withdraw response:', response); + + // Wait for confirmation + let status = await server.getTransaction(response.hash); + while (status.status === 'PENDING' || status.status === 'NOT_FOUND') { + await new Promise(resolve => setTimeout(resolve, 1000)); + status = await server.getTransaction(response.hash); + } + + if (status.status === 'SUCCESS') { + console.log('Withdrawal successful!'); + return status.returnValue; + } else { + throw new Error(`Transaction failed: ${status.status}`); + } + } catch (error) { + console.error('Error withdrawing:', error); + throw error; + } +} + +/** + * Example 5: Check if nullifier is spent + */ +async function isNullifierSpent(nullifierHash: string): Promise { + try { + const sourceAccount = await server.getAccount(StellarSdk.Keypair.random().publicKey()); + + const nullifierBytes = Buffer.from(nullifierHash.replace('0x', ''), 'hex'); + const nullifierScVal = StellarSdk.xdr.ScVal.scvBytes(nullifierBytes); + + const tx = new StellarSdk.TransactionBuilder(sourceAccount, { + fee: StellarSdk.BASE_FEE, + networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation(contract.call('is_spent', nullifierScVal)) + .setTimeout(30) + .build(); + + const simulated = await server.simulateTransaction(tx); + + if (simulated.result) { + return simulated.result.retval.value(); + } + + return false; + } catch (error) { + console.error('Error checking nullifier:', error); + throw error; + } +} + +/** + * Example 6: Get deposit count + */ +async function getDepositCount(): Promise { + try { + const sourceAccount = await server.getAccount(StellarSdk.Keypair.random().publicKey()); + + const tx = new StellarSdk.TransactionBuilder(sourceAccount, { + fee: StellarSdk.BASE_FEE, + networkPassphrase: NETWORK_PASSPHRASE, + }) + .addOperation(contract.call('deposit_count')) + .setTimeout(30) + .build(); + + const simulated = await server.simulateTransaction(tx); + + if (simulated.result) { + return simulated.result.retval.value(); + } + + return 0; + } catch (error) { + console.error('Error getting deposit count:', error); + throw error; + } +} + +// Export functions +export { + getCurrentRoot, + deposit, + generateCommitment, + withdraw, + isNullifierSpent, + getDepositCount, +}; diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..14aac2f --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,724 @@ +openapi: 3.0.3 +info: + title: PrivacyLayer API + version: 1.0.0 + description: | + # PrivacyLayer - Stellar Soroban Privacy Pool API + + The first ZK-proof shielded pool on Stellar Soroban, powered by Protocol 25's native BN254 and Poseidon cryptographic primitives. + + ## Overview + + PrivacyLayer enables compliance-forward private transactions on Stellar. Users deposit fixed-denomination XLM or USDC into a shielded pool, then withdraw to any address using a zero-knowledge proof — with no on-chain link between deposit and withdrawal. + + ## Architecture + + - **Deposit**: User commits funds with a Poseidon hash commitment + - **Merkle Tree**: Commitments stored in on-chain incremental Merkle tree (depth 20) + - **Withdraw**: User generates Groth16 ZK proof and withdraws to any address + - **Verification**: On-chain Groth16 verification using BN254 pairing + + ## Contract Methods + + This API documents all public methods of the PrivacyPool Soroban smart contract. + + contact: + name: PrivacyLayer Team + url: https://github.com/ANAVHEOBA/PrivacyLayer + license: + name: MIT + url: https://github.com/ANAVHEOBA/PrivacyLayer/blob/main/LICENSE + +servers: + - url: https://soroban-testnet.stellar.org + description: Stellar Testnet + - url: https://soroban-mainnet.stellar.org + description: Stellar Mainnet (when deployed) + +tags: + - name: Initialization + description: Contract initialization operations + - name: Core Operations + description: Deposit and withdrawal operations + - name: View Functions + description: Read-only query functions + - name: Admin Functions + description: Administrative operations (admin only) + +paths: + /initialize: + post: + tags: + - Initialization + summary: Initialize the privacy pool + description: | + Initialize the privacy pool contract. Must be called once before any deposits or withdrawals. + Sets the admin, token, denomination, and verifying key. + + **Requirements:** + - Can only be called once + - Must be called before any other operations + + operationId: initialize + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - admin + - token + - denomination + - verifying_key + properties: + admin: + type: string + description: Stellar address of the pool administrator + example: "GABC...XYZ" + token: + type: string + description: Token contract address (XLM native or USDC) + example: "CDLZ...ABC" + denomination: + $ref: '#/components/schemas/Denomination' + verifying_key: + $ref: '#/components/schemas/VerifyingKey' + responses: + '200': + description: Pool initialized successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + '400': + $ref: '#/components/responses/BadRequest' + '409': + description: Pool already initialized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /deposit: + post: + tags: + - Core Operations + summary: Deposit into the shielded pool + description: | + Deposit funds into the privacy pool. Transfers the denomination amount from the sender + and inserts the commitment into the Merkle tree. + + **Process:** + 1. Generate a random nullifier and secret off-chain + 2. Compute commitment = Poseidon2(nullifier, secret) + 3. Call this endpoint with the commitment + 4. Save the nullifier and secret securely (needed for withdrawal) + + **Returns:** + - Leaf index in the Merkle tree + - Updated Merkle root + + operationId: deposit + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - from + - commitment + properties: + from: + type: string + description: Stellar address of the depositor + example: "GABC...XYZ" + commitment: + type: string + format: hex + description: 32-byte Poseidon2 commitment hash (hex-encoded) + example: "0x1234567890abcdef..." + minLength: 66 + maxLength: 66 + responses: + '200': + description: Deposit successful + content: + application/json: + schema: + type: object + properties: + leaf_index: + type: integer + format: uint32 + description: Index of the inserted leaf in the Merkle tree + example: 42 + merkle_root: + type: string + format: hex + description: Updated Merkle root after insertion + example: "0xabcdef1234567890..." + '400': + $ref: '#/components/responses/BadRequest' + '403': + description: Pool is paused + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /withdraw: + post: + tags: + - Core Operations + summary: Withdraw from the shielded pool using a ZK proof + description: | + Withdraw funds from the privacy pool to any address using a zero-knowledge proof. + + **Process:** + 1. Sync the Merkle tree state from the contract + 2. Generate a Merkle proof for your commitment + 3. Generate a Groth16 ZK proof using the Noir circuit + 4. Call this endpoint with the proof and public inputs + + **Verification:** + - Proof is valid (Groth16 pairing check) + - Root is in the historical root buffer + - Nullifier has not been spent + - Amount matches denomination + + operationId: withdraw + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - proof + - public_inputs + properties: + proof: + $ref: '#/components/schemas/Proof' + public_inputs: + $ref: '#/components/schemas/PublicInputs' + responses: + '200': + description: Withdrawal successful + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + nullifier_hash: + type: string + format: hex + description: Nullifier hash that was marked as spent + example: "0x9876543210fedcba..." + '400': + $ref: '#/components/responses/BadRequest' + '403': + description: Pool is paused or proof verification failed + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: Nullifier already spent (double-spend attempt) + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /get_root: + get: + tags: + - View Functions + summary: Get the current Merkle root + description: Returns the most recent Merkle root of the commitment tree + operationId: getRoot + responses: + '200': + description: Current Merkle root + content: + application/json: + schema: + type: object + properties: + root: + type: string + format: hex + description: Current Merkle root (32 bytes, hex-encoded) + example: "0xabcdef1234567890..." + + /deposit_count: + get: + tags: + - View Functions + summary: Get total number of deposits + description: Returns the total number of deposits made to the pool + operationId: depositCount + responses: + '200': + description: Deposit count + content: + application/json: + schema: + type: object + properties: + count: + type: integer + format: uint32 + description: Total number of deposits + example: 137 + + /is_known_root: + get: + tags: + - View Functions + summary: Check if a root is in the historical root buffer + description: | + Verify if a given Merkle root exists in the historical root buffer. + Used during withdrawal to validate that the proof was generated against a valid historical state. + operationId: isKnownRoot + parameters: + - name: root + in: query + required: true + schema: + type: string + format: hex + description: Merkle root to check (32 bytes, hex-encoded) + example: "0xabcdef1234567890..." + responses: + '200': + description: Root validity check result + content: + application/json: + schema: + type: object + properties: + is_known: + type: boolean + description: True if root is in the historical buffer + example: true + + /is_spent: + get: + tags: + - View Functions + summary: Check if a nullifier has been spent + description: | + Check if a nullifier hash has already been used in a withdrawal. + Prevents double-spending. + operationId: isSpent + parameters: + - name: nullifier_hash + in: query + required: true + schema: + type: string + format: hex + description: Nullifier hash to check (32 bytes, hex-encoded) + example: "0x9876543210fedcba..." + responses: + '200': + description: Nullifier spent status + content: + application/json: + schema: + type: object + properties: + is_spent: + type: boolean + description: True if nullifier has been spent + example: false + + /get_config: + get: + tags: + - View Functions + summary: Get pool configuration + description: Returns the current pool configuration including admin, token, denomination, and pause status + operationId: getConfig + responses: + '200': + description: Pool configuration + content: + application/json: + schema: + $ref: '#/components/schemas/PoolConfig' + + /pause: + post: + tags: + - Admin Functions + summary: Pause the pool + description: | + Pause all deposits and withdrawals. Admin only. + + **Requirements:** + - Caller must be the admin address + - Pool must not already be paused + operationId: pause + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - admin + properties: + admin: + type: string + description: Admin address (must match configured admin) + example: "GABC...XYZ" + responses: + '200': + description: Pool paused successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + '401': + description: Unauthorized - caller is not admin + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /unpause: + post: + tags: + - Admin Functions + summary: Unpause the pool + description: | + Resume deposits and withdrawals. Admin only. + + **Requirements:** + - Caller must be the admin address + - Pool must be paused + operationId: unpause + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - admin + properties: + admin: + type: string + description: Admin address (must match configured admin) + example: "GABC...XYZ" + responses: + '200': + description: Pool unpaused successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + '401': + description: Unauthorized - caller is not admin + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + /set_verifying_key: + post: + tags: + - Admin Functions + summary: Update the Groth16 verifying key + description: | + Update the verifying key used for proof verification. Admin only. + + **Use case:** Circuit upgrades or bug fixes that require a new verifying key. + + **Requirements:** + - Caller must be the admin address + - New verifying key must be valid + operationId: setVerifyingKey + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - admin + - new_verifying_key + properties: + admin: + type: string + description: Admin address (must match configured admin) + example: "GABC...XYZ" + new_verifying_key: + $ref: '#/components/schemas/VerifyingKey' + responses: + '200': + description: Verifying key updated successfully + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + example: true + '401': + description: Unauthorized - caller is not admin + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + +components: + schemas: + Denomination: + type: string + enum: + - Xlm10 + - Xlm100 + - Xlm1000 + - Usdc100 + - Usdc1000 + description: | + Fixed denomination amounts supported by the pool. + Using fixed denominations prevents amount-based correlation attacks. + + - **Xlm10**: 10 XLM (100,000,000 stroops) + - **Xlm100**: 100 XLM (1,000,000,000 stroops) + - **Xlm1000**: 1000 XLM (10,000,000,000 stroops) + - **Usdc100**: 100 USDC (100,000,000 microunits, 6 decimals) + - **Usdc1000**: 1000 USDC (1,000,000,000 microunits) + example: "Xlm100" + + VerifyingKey: + type: object + description: | + Groth16 verifying key for BN254 curve. + Used to verify withdrawal proofs on-chain. + + **Structure:** + - alpha_g1: G1 point (64 bytes) + - beta_g2: G2 point (128 bytes) + - gamma_g2: G2 point (128 bytes) + - delta_g2: G2 point (128 bytes) + - gamma_abc_g1: Array of G1 points (7 points × 64 bytes = 448 bytes) + + Total: 896 bytes + required: + - alpha_g1 + - beta_g2 + - gamma_g2 + - delta_g2 + - gamma_abc_g1 + properties: + alpha_g1: + type: string + format: hex + description: G1 point alpha (64 bytes, hex-encoded) + minLength: 130 + maxLength: 130 + beta_g2: + type: string + format: hex + description: G2 point beta (128 bytes, hex-encoded) + minLength: 258 + maxLength: 258 + gamma_g2: + type: string + format: hex + description: G2 point gamma (128 bytes, hex-encoded) + minLength: 258 + maxLength: 258 + delta_g2: + type: string + format: hex + description: G2 point delta (128 bytes, hex-encoded) + minLength: 258 + maxLength: 258 + gamma_abc_g1: + type: array + description: Array of G1 points for public input combination (7 points) + items: + type: string + format: hex + minLength: 130 + maxLength: 130 + minItems: 7 + maxItems: 7 + + Proof: + type: object + description: | + Groth16 proof — three elliptic curve points on BN254. + + **Structure:** + - a: G1 point (64 bytes) + - b: G2 point (128 bytes) + - c: G1 point (64 bytes) + + Total: 256 bytes + required: + - a + - b + - c + properties: + a: + type: string + format: hex + description: G1 point A (64 bytes, hex-encoded) + minLength: 130 + maxLength: 130 + b: + type: string + format: hex + description: G2 point B (128 bytes, hex-encoded) + minLength: 258 + maxLength: 258 + c: + type: string + format: hex + description: G1 point C (64 bytes, hex-encoded) + minLength: 130 + maxLength: 130 + + PublicInputs: + type: object + description: | + Public inputs to the withdrawal Groth16 proof. + Order must match the circuit's public input ordering. + required: + - root + - nullifier_hash + - recipient + - amount + - relayer + - fee + properties: + root: + type: string + format: hex + description: Merkle root at deposit time (must be a known historical root) + example: "0xabcdef1234567890..." + minLength: 66 + maxLength: 66 + nullifier_hash: + type: string + format: hex + description: Poseidon2(nullifier, root) — prevents double-spend + example: "0x9876543210fedcba..." + minLength: 66 + maxLength: 66 + recipient: + type: string + format: hex + description: Stellar address of withdrawal recipient (as field element) + example: "0x1234567890abcdef..." + minLength: 66 + maxLength: 66 + amount: + type: string + format: hex + description: Denomination amount being withdrawn (as field element) + example: "0x0000000000000000..." + minLength: 66 + maxLength: 66 + relayer: + type: string + format: hex + description: Relayer address (zero if none) + example: "0x0000000000000000..." + minLength: 66 + maxLength: 66 + fee: + type: string + format: hex + description: Relayer fee (zero if none) + example: "0x0000000000000000..." + minLength: 66 + maxLength: 66 + + PoolConfig: + type: object + description: Pool configuration — set at initialization + properties: + admin: + type: string + description: Pool administrator address + example: "GABC...XYZ" + token: + type: string + description: Token contract address + example: "CDLZ...ABC" + denomination: + $ref: '#/components/schemas/Denomination' + tree_depth: + type: integer + format: uint32 + description: Merkle tree depth (always 20) + example: 20 + root_history_size: + type: integer + format: uint32 + description: Maximum number of historical roots to keep + example: 100 + paused: + type: boolean + description: Whether deposits/withdrawals are paused + example: false + + Error: + type: object + properties: + error: + type: string + description: Error message + example: "Pool is paused" + code: + type: string + description: Error code + example: "POOL_PAUSED" + + responses: + BadRequest: + description: Invalid request parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + error: "Invalid commitment format" + code: "INVALID_INPUT" + + securitySchemes: + StellarAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: | + Stellar transaction signing authentication. + Transactions must be signed by the appropriate Stellar account. + +security: + - StellarAuth: [] diff --git a/docs/postman_collection.json b/docs/postman_collection.json new file mode 100644 index 0000000..d726d7b --- /dev/null +++ b/docs/postman_collection.json @@ -0,0 +1,261 @@ +{ + "info": { + "name": "PrivacyLayer API", + "description": "Complete API collection for PrivacyLayer privacy pool on Stellar Soroban", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "version": "1.0.0" + }, + "variable": [ + { + "key": "CONTRACT_ADDRESS", + "value": "CCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "type": "string" + }, + { + "key": "ADMIN_ADDRESS", + "value": "GABC...XYZ", + "type": "string" + }, + { + "key": "USER_ADDRESS", + "value": "GDEF...ABC", + "type": "string" + }, + { + "key": "NETWORK", + "value": "testnet", + "type": "string" + }, + { + "key": "RPC_URL", + "value": "https://soroban-testnet.stellar.org", + "type": "string" + } + ], + "item": [ + { + "name": "Initialization", + "item": [ + { + "name": "Initialize Pool", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": \"{{ADMIN_ADDRESS}}\",\n \"token\": \"CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC\",\n \"denomination\": \"Xlm100\",\n \"verifying_key\": {\n \"alpha_g1\": \"0x...\",\n \"beta_g2\": \"0x...\",\n \"gamma_g2\": \"0x...\",\n \"delta_g2\": \"0x...\",\n \"gamma_abc_g1\": [\"0x...\", \"0x...\", \"0x...\", \"0x...\", \"0x...\", \"0x...\", \"0x...\"]\n }\n}" + }, + "url": { + "raw": "{{RPC_URL}}/initialize", + "host": ["{{RPC_URL}}"], + "path": ["initialize"] + }, + "description": "Initialize the privacy pool with admin, token, denomination, and verifying key" + } + } + ] + }, + { + "name": "Core Operations", + "item": [ + { + "name": "Deposit", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"from\": \"{{USER_ADDRESS}}\",\n \"commitment\": \"0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef\"\n}" + }, + "url": { + "raw": "{{RPC_URL}}/deposit", + "host": ["{{RPC_URL}}"], + "path": ["deposit"] + }, + "description": "Deposit funds into the shielded pool with a Poseidon commitment" + } + }, + { + "name": "Withdraw", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"proof\": {\n \"a\": \"0x...\",\n \"b\": \"0x...\",\n \"c\": \"0x...\"\n },\n \"public_inputs\": {\n \"root\": \"0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890\",\n \"nullifier_hash\": \"0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba\",\n \"recipient\": \"0x1111111111111111111111111111111111111111111111111111111111111111\",\n \"amount\": \"0x0000000000000000000000000000000000000000000000000000003b9aca00\",\n \"relayer\": \"0x0000000000000000000000000000000000000000000000000000000000000000\",\n \"fee\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"\n }\n}" + }, + "url": { + "raw": "{{RPC_URL}}/withdraw", + "host": ["{{RPC_URL}}"], + "path": ["withdraw"] + }, + "description": "Withdraw funds using a zero-knowledge proof" + } + } + ] + }, + { + "name": "View Functions", + "item": [ + { + "name": "Get Root", + "request": { + "method": "GET", + "url": { + "raw": "{{RPC_URL}}/get_root", + "host": ["{{RPC_URL}}"], + "path": ["get_root"] + }, + "description": "Get the current Merkle root" + } + }, + { + "name": "Deposit Count", + "request": { + "method": "GET", + "url": { + "raw": "{{RPC_URL}}/deposit_count", + "host": ["{{RPC_URL}}"], + "path": ["deposit_count"] + }, + "description": "Get total number of deposits" + } + }, + { + "name": "Is Known Root", + "request": { + "method": "GET", + "url": { + "raw": "{{RPC_URL}}/is_known_root?root=0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890", + "host": ["{{RPC_URL}}"], + "path": ["is_known_root"], + "query": [ + { + "key": "root", + "value": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + } + ] + }, + "description": "Check if a Merkle root is in the historical buffer" + } + }, + { + "name": "Is Spent", + "request": { + "method": "GET", + "url": { + "raw": "{{RPC_URL}}/is_spent?nullifier_hash=0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba", + "host": ["{{RPC_URL}}"], + "path": ["is_spent"], + "query": [ + { + "key": "nullifier_hash", + "value": "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba" + } + ] + }, + "description": "Check if a nullifier has been spent" + } + }, + { + "name": "Get Config", + "request": { + "method": "GET", + "url": { + "raw": "{{RPC_URL}}/get_config", + "host": ["{{RPC_URL}}"], + "path": ["get_config"] + }, + "description": "Get pool configuration" + } + } + ] + }, + { + "name": "Admin Functions", + "item": [ + { + "name": "Pause Pool", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": \"{{ADMIN_ADDRESS}}\"\n}" + }, + "url": { + "raw": "{{RPC_URL}}/pause", + "host": ["{{RPC_URL}}"], + "path": ["pause"] + }, + "description": "Pause all deposits and withdrawals (admin only)" + } + }, + { + "name": "Unpause Pool", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": \"{{ADMIN_ADDRESS}}\"\n}" + }, + "url": { + "raw": "{{RPC_URL}}/unpause", + "host": ["{{RPC_URL}}"], + "path": ["unpause"] + }, + "description": "Resume deposits and withdrawals (admin only)" + } + }, + { + "name": "Set Verifying Key", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"admin\": \"{{ADMIN_ADDRESS}}\",\n \"new_verifying_key\": {\n \"alpha_g1\": \"0x...\",\n \"beta_g2\": \"0x...\",\n \"gamma_g2\": \"0x...\",\n \"delta_g2\": \"0x...\",\n \"gamma_abc_g1\": [\"0x...\", \"0x...\", \"0x...\", \"0x...\", \"0x...\", \"0x...\", \"0x...\"]\n }\n}" + }, + "url": { + "raw": "{{RPC_URL}}/set_verifying_key", + "host": ["{{RPC_URL}}"], + "path": ["set_verifying_key"] + }, + "description": "Update the Groth16 verifying key (admin only)" + } + } + ] + } + ] +}