Skip to content
Merged
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
16 changes: 16 additions & 0 deletions package-lock.json

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

73 changes: 49 additions & 24 deletions src/content/docs/concepts/privacy-levels.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,27 @@ Solver view:

Full privacy with selective disclosure for authorized auditors.

```typescript
const viewingKey = sip.generateViewingKey('/audit')
COMPLIANT mode requires a viewing key. The `IntentBuilder` covers input/output/privacy, but the viewing key is supplied through `createShieldedIntent` (the function the builder delegates to) via the `viewingKey` field of `CreateIntentParams`:

const intent = await sip
.intent()
.input('solana', 'SOL', 5_000_000_000n)
.output('near', 'NEAR')
.privacy(PrivacyLevel.COMPLIANT)
.viewingKey(viewingKey)
.build()
```typescript
import { createShieldedIntent, generateViewingKey, PrivacyLevel } from '@sip-protocol/sdk'

const viewingKey = generateViewingKey('m/0/audit')

const intent = await createShieldedIntent({
input: {
asset: { chain: 'solana', symbol: 'SOL', address: null, decimals: 9 },
amount: 5_000_000_000n,
},
output: {
asset: { chain: 'near', symbol: 'NEAR', address: null, decimals: 24 },
minAmount: 0n,
maxSlippage: 0.01,
},
privacy: PrivacyLevel.COMPLIANT,
recipientMetaAddress,
viewingKey: viewingKey.key,
})
```

### Visibility
Expand Down Expand Up @@ -186,30 +197,44 @@ Once data is committed/hidden, it cannot be revealed without user cooperation.

## SDK Usage

```typescript
import { SIP, PrivacyLevel } from '@sip-protocol/sdk'
`createShieldedIntent` accepts a `CreateIntentParams` object: `{ input, output, privacy, recipientMetaAddress?, viewingKey?, ttl? }`. `input`/`output` carry the asset and amount; the privacy level is set via `privacy`.

const sip = new SIP({ network: 'testnet' })
```typescript
import { createShieldedIntent, generateViewingKey, PrivacyLevel } from '@sip-protocol/sdk'

const input = {
asset: { chain: 'near', symbol: 'NEAR', address: null, decimals: 24 },
amount: 100n,
}
const output = {
asset: { chain: 'ethereum', symbol: 'ETH', address: null, decimals: 18 },
minAmount: 0n,
maxSlippage: 0.01,
}

// Transparent
const transparent = await sip.createIntent({
privacyLevel: PrivacyLevel.TRANSPARENT,
sender: '0x...',
recipient: '0x...',
inputAmount: 100n,
const transparent = await createShieldedIntent({
input,
output,
privacy: PrivacyLevel.TRANSPARENT,
})

// Shielded - SDK generates commitments, stealth, proofs
const shielded = await sip.createIntent({
privacyLevel: PrivacyLevel.SHIELDED,
inputAmount: 100n,
const shielded = await createShieldedIntent({
input,
output,
privacy: PrivacyLevel.SHIELDED,
recipientMetaAddress,
})

// Compliant - SDK adds encrypted viewing data
const compliant = await sip.createIntent({
privacyLevel: PrivacyLevel.COMPLIANT,
inputAmount: 100n,
auditorKeyHash: '0x...',
const viewingKey = generateViewingKey('m/0/audit')
const compliant = await createShieldedIntent({
input,
output,
privacy: PrivacyLevel.COMPLIANT,
recipientMetaAddress,
viewingKey: viewingKey.key,
})
```

Expand Down
16 changes: 8 additions & 8 deletions src/content/docs/concepts/stealth-address.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,13 @@ const isMine = checkStealthAddress(

if (isMine) {
// Derive private key to claim funds
const privateKey = deriveStealthPrivateKey(
// (the ephemeral public key is read from stealthAddress.ephemeralPublicKey internally)
const recovery = deriveStealthPrivateKey(
stealthAddress,
ephemeralPublicKey,
spendingPrivateKey,
viewingPrivateKey
)
// recovery.privateKey is the spendable key for stealthAddress
}
```

Expand Down Expand Up @@ -281,13 +282,12 @@ const isMine = checkStealthAddress(

// Recipient claims
if (isMine) {
const pk = deriveStealthPrivateKey(
const recovery = deriveStealthPrivateKey(
stealthAddress,
ephemeralPublicKey,
meta.spendingPrivateKey,
meta.viewingPrivateKey
)
// Use pk to spend from stealthAddress
// Use recovery.privateKey to spend from stealthAddress
}
```

Expand All @@ -307,9 +307,9 @@ SIP supports multiple elliptic curves to enable stealth addresses across differe
const ethMeta = generateStealthMetaAddress('ethereum')
const btcMeta = generateStealthMetaAddress('bitcoin')

// ed25519 - for Solana, NEAR
const solMeta = generateStealthMetaAddress('solana', { curve: 'ed25519' })
const nearMeta = generateStealthMetaAddress('near', { curve: 'ed25519' })
// ed25519 - for Solana, NEAR (curve is auto-selected from the chain)
const solMeta = generateStealthMetaAddress('solana')
const nearMeta = generateStealthMetaAddress('near')
```

### Curve Differences
Expand Down
52 changes: 29 additions & 23 deletions src/content/docs/concepts/viewing-key.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,12 @@ Reveals ONLY the specific transaction.
```typescript
import { encryptForViewing, decryptWithViewing } from '@sip-protocol/sdk'

// TransactionData = { sender: string, recipient: string, amount: string, timestamp: number }
const txData = {
sender: '0xabc...',
recipient: '0xdef...',
amount: 1000n,
asset: 'ETH'
amount: '1000',
timestamp: Date.now(),
}

// Encrypt for viewing key holder
Expand All @@ -132,14 +133,13 @@ const decrypted = decryptWithViewing(encrypted, viewingKey)

### Multi-Key Encryption

Encrypt for multiple authorized viewers:
`encryptForViewing` encrypts for a single `ViewingKey`. To grant access to multiple authorized viewers, encrypt the data once per key — each holder can then decrypt their own blob independently:

```typescript
const encrypted = encryptForViewing(txData, [
auditorKey,
complianceKey,
taxKey
])
const viewers = [auditorKey, complianceKey, taxKey]

const encryptedBlobs = viewers.map((key) => encryptForViewing(txData, key))
// encryptedBlobs[0] decrypts with auditorKey, [1] with complianceKey, etc.
```

Each key holder can decrypt independently.
Expand All @@ -150,25 +150,30 @@ A ViewingProof demonstrates that decrypted data is authentic without revealing t

```typescript
interface ViewingProof {
intentHash: string
revealedData: TransactionData
commitmentProof: {
inputAmountProof: ZKProof
outputAmountProof: ZKProof
/** The decrypted transaction details */
transaction: {
sender: string
recipient: string
amount: string
timestamp: number
}
viewingKeyHash: string
proofTimestamp: number
/** Proof that decryption was done correctly */
proof: ZKProof
}
```

Verifier checks:
1. ZK proof is valid
2. Revealed amounts match on-chain commitments
3. Viewing key hash matches authorized viewer
3. The proof corresponds to the authorized viewer's key

## Access Control

### Key Registration
:::note[Conceptual / roadmap]
The grant and revocation APIs below are conceptual — they describe a planned compliance-module capability, not shipped top-level SDK exports. The current SDK does not export `ViewingKeyGrant` or a `revokeViewingKey` function. Access control today is enforced operationally: hierarchical key derivation (`deriveViewingKey`) limits scope (per-transaction TVKs reveal only one tx), and a key is "revoked" by ceasing to share it and rotating the parent. Granular, on-chain grant tracking is expected to live in the compliance module (`ComplianceManager`).
:::

### Key Registration (conceptual)

```typescript
interface ViewingKeyGrant {
Expand All @@ -185,11 +190,12 @@ interface ViewingKeyGrant {
}
```

### Revocation
### Revocation (conceptual)

Keys can be revoked but previously-viewed data cannot be "un-revealed":

```typescript
// Planned compliance-module API (not a shipped SDK export)
await revokeViewingKey(grantId)
```

Expand Down Expand Up @@ -289,19 +295,19 @@ const auditKey = deriveViewingKey(masterKey, '/audit/2024')
const taxKey = deriveViewingKey(masterKey, '/tax/quarterly')

// Encrypt transaction for auditor
// TransactionData = { sender: string, recipient: string, amount: string, timestamp: number }
const txData = {
intentHash: '0x123...',
inputAmount: 1000n,
outputAmount: 950n,
sender: '0xabc...',
recipient: '0xdef...'
recipient: '0xdef...',
amount: '1000',
timestamp: Date.now(),
}

const encrypted = encryptForViewing(txData, auditKey)

// Auditor decrypts
const revealed = decryptWithViewing(encrypted, auditKey)
console.log(revealed.inputAmount) // 1000n
console.log(revealed.amount) // "1000"
```

## Trust Model
Expand Down
12 changes: 6 additions & 6 deletions src/content/docs/cookbook/01-basic-swap.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -170,18 +170,18 @@ basicSwap()

### Pitfall 1: Forgetting to Generate Stealth Keys

**Problem:** Calling `.recipient()` without generating keys first.
**Problem:** Building a shielded intent without setting a recipient.

```typescript
// ❌ Wrong - no stealth keys generated
// ❌ Wrong - no recipient set
const intent = await sip.intent()
.input('solana', 'SOL', 1_000_000_000n)
.output('zcash', 'ZEC', 50_000_000n)
.privacy(PrivacyLevel.SHIELDED)
.build() // Error: No recipient for shielded mode
.build() // Builds with a placeholder recipient - real recipient enforced at production getQuotes
```

**Solution:** Always generate keys before using shielded mode.
**Solution:** Always generate keys and set a recipient before using shielded mode.

```typescript
// ✅ Correct
Expand All @@ -201,8 +201,8 @@ const intent = await sip.intent()
**Problem:** Passing decimal numbers instead of smallest unit bigints.

```typescript
// ❌ Wrong - using decimal SOL
.input('solana', 'SOL', 1.5) // Will floor to 1 lamport!
// ❌ Wrong - the number path always multiplies by 1e18, ignoring token decimals
.input('solana', 'SOL', 1.5) // Becomes 1.5 * 1e18, not 1.5 SOL (9 decimals) - off by 1e9
```

**Solution:** Always use the smallest unit (lamports, zatoshis, wei, etc.) as bigint.
Expand Down
43 changes: 20 additions & 23 deletions src/content/docs/cookbook/02-privacy-levels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ const transparentIntent = await sip.intent()
console.log('Privacy level:', transparentIntent.privacyLevel)
// Output: "transparent"

// No stealth address needed for transparent mode
console.log('Has stealth address:', !!transparentIntent.recipientStealth)
// Output: false (placeholder only)
// No real stealth address in transparent mode - a placeholder is used
console.log('Is placeholder stealth:', transparentIntent.recipientStealth.address === '0x0')
// Output: true (placeholder address '0x0', not a real stealth address)

// Execute and get public transaction hash
const quotes = await sip.getQuotes(transparentIntent)
Expand Down Expand Up @@ -96,9 +96,17 @@ console.log('Transaction hash:', result.txHash)

### Step 3: Compliant Mode (Privacy + Audit)

Use compliant mode when you need privacy but also audit capability:
Use compliant mode when you need privacy but also audit capability.

:::note[Compliant mode needs `createShieldedIntent`]
The fluent `sip.intent()` builder has no viewing-key setter, and building a `COMPLIANT`
intent without a viewing key throws (`viewingKey is required for compliant mode`). Use
`createShieldedIntent` directly so you can pass `viewingKey` at creation time.
:::

```typescript
import { createShieldedIntent } from '@sip-protocol/sdk'

// Generate viewing key for auditors
const viewingKey = sip.generateViewingKey('/m/44/501/0/compliance')

Expand All @@ -112,19 +120,7 @@ console.log('Viewing key:', {
sip.generateStealthKeys('near', 'compliant-wallet')
const recipientAddress = sip.getStealthAddress()!

// Compliant swap - private but auditable
const compliantIntent = await sip.intent()
.input('solana', 'SOL', 1_000_000_000n)
.output('near', 'NEAR', 100_000_000n)
.privacy(PrivacyLevel.COMPLIANT)
.recipient(recipientAddress)
.slippage(1)
.build()

// But wait - we need to pass the viewing key during creation!
// Use createShieldedIntent directly for more control:
import { createShieldedIntent } from '@sip-protocol/sdk'

// Compliant swap - private but auditable (viewing key passed at creation)
const compliantIntentWithKey = await createShieldedIntent({
input: {
asset: { chain: 'solana', symbol: 'SOL', address: null, decimals: 9 },
Expand Down Expand Up @@ -251,21 +247,22 @@ const intent = await sip.intent()
.build()
```

### Pitfall 2: Using Compliant Mode Without Viewing Key
### Pitfall 2: Building Compliant Mode with the Fluent Builder

**Problem:** Setting privacy to COMPLIANT but not providing a viewing key.
**Problem:** Trying to create a COMPLIANT intent through `sip.intent()`. The builder has
no viewing-key setter, and building COMPLIANT without one throws.

```typescript
// ❌ Wrong - compliant mode requires viewing key
// ❌ Wrong - the builder cannot attach a viewing key, so this throws
const intent = await sip.intent()
.privacy(PrivacyLevel.COMPLIANT)
.build() // Error: viewingKey required for compliant mode
.build() // Error: viewingKey is required for compliant mode
```

**Solution:** Generate and provide viewing key for compliant mode.
**Solution:** Use `createShieldedIntent` so you can pass the viewing key at creation.

```typescript
// ✅ Correct
// ✅ Correct - createShieldedIntent accepts viewingKey
const viewingKey = sip.generateViewingKey()
const intent = await createShieldedIntent({
// ... other params
Expand Down
Loading
Loading