Multi-Party Disclosure
Overview
Section titled “Overview”Multi-party disclosure allows you to encrypt the same transaction data for multiple authorized parties, each with their own viewing key. This is essential for complex compliance scenarios involving multiple regulators or stakeholders.
Prerequisites
Section titled “Prerequisites”- Completed Viewing Key Management
- Understanding of key derivation
@sip-protocol/sdkinstalled
Step-by-Step
Section titled “Step-by-Step”Step 1: Generate Keys for Multiple Parties
Section titled “Step 1: Generate Keys for Multiple Parties”Create separate viewing keys for each party that needs access:
import { SIP } from '@sip-protocol/sdk'
const sip = new SIP({ network: 'testnet', mode: 'demo' })
// Generate master key for the organizationconst masterKey = sip.generateViewingKey('/m/44/501/0/company')
// Derive keys for different partiesconst parties = { taxAuthority: sip.deriveViewingKey(masterKey, 'disclosure/tax/federal'), auditor: sip.deriveViewingKey(masterKey, 'disclosure/audit/external'), compliance: sip.deriveViewingKey(masterKey, 'disclosure/compliance/aml'), legal: sip.deriveViewingKey(masterKey, 'disclosure/legal/general'),}
console.log('Generated keys for parties:', Object.keys(parties))// Output: ['taxAuthority', 'auditor', 'compliance', 'legal']Step 2: Encrypt for Multiple Parties
Section titled “Step 2: Encrypt for Multiple Parties”Encrypt the same transaction data separately for each party:
import { encryptForViewing, type TransactionData } from '@sip-protocol/sdk'
// Transaction data to discloseconst transactionData: TransactionData = { sender: '0xCompanyTreasuryWallet', recipient: '0xVendorPaymentAddress', amount: '5000000000', // 5 SOL timestamp: Math.floor(Date.now() / 1000),}
// Encrypt separately for each partyconst encryptedForParties = { forTax: encryptForViewing(transactionData, parties.taxAuthority), forAuditor: encryptForViewing(transactionData, parties.auditor), forCompliance: encryptForViewing(transactionData, parties.compliance), forLegal: encryptForViewing(transactionData, parties.legal),}
console.log('Transaction encrypted for', Object.keys(encryptedForParties).length, 'parties')
// Each encryption is unique - different ciphertext, nonce, key hashconsole.log('Ciphertexts are different:', encryptedForParties.forTax.ciphertext !== encryptedForParties.forAuditor.ciphertext) // trueStep 3: Store Encrypted Data with Metadata
Section titled “Step 3: Store Encrypted Data with Metadata”Store encrypted data with metadata about which parties can access it:
interface DisclosureRecord { transactionId: string createdAt: number encryptedData: { ciphertext: string nonce: string viewingKeyHash: string } authorizedParty: string expiresAt?: number}
// Create disclosure recordsconst disclosureRecords: DisclosureRecord[] = [ { transactionId: 'tx-001', createdAt: Date.now(), encryptedData: encryptedForParties.forTax, authorizedParty: 'Tax Authority (Federal)', expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year }, { transactionId: 'tx-001', createdAt: Date.now(), encryptedData: encryptedForParties.forAuditor, authorizedParty: 'External Auditor', expiresAt: Date.now() + (90 * 24 * 60 * 60 * 1000), // 90 days }, { transactionId: 'tx-001', createdAt: Date.now(), encryptedData: encryptedForParties.forCompliance, authorizedParty: 'Compliance Officer (AML)', }, { transactionId: 'tx-001', createdAt: Date.now(), encryptedData: encryptedForParties.forLegal, authorizedParty: 'Legal Department', },]
// Store in database// await database.disclosures.insertMany(disclosureRecords)
console.log('Stored', disclosureRecords.length, 'disclosure records')Step 4: Party-Specific Decryption
Section titled “Step 4: Party-Specific Decryption”Each party can only decrypt their copy of the data:
import { decryptWithViewing } from '@sip-protocol/sdk'
// Tax authority decrypts their copyconsole.log('\n--- Tax Authority Access ---')try { const taxData = decryptWithViewing( encryptedForParties.forTax, parties.taxAuthority ) console.log('Tax authority can see:', { amount: taxData.amount, timestamp: new Date(taxData.timestamp * 1000).toISOString(), })} catch (error) { console.error('Tax authority: Access denied')}
// Tax authority CANNOT decrypt auditor's copyconsole.log('\n--- Tax Authority Tries Auditor Data ---')try { decryptWithViewing(encryptedForParties.forAuditor, parties.taxAuthority) console.log('Tax authority: Unexpected access to auditor data!')} catch (error) { console.log('Tax authority: Cannot access auditor data ✓')}
// Auditor decrypts their copyconsole.log('\n--- External Auditor Access ---')try { const auditorData = decryptWithViewing( encryptedForParties.forAuditor, parties.auditor ) console.log('Auditor can see:', { sender: auditorData.sender, recipient: auditorData.recipient, amount: auditorData.amount, })} catch (error) { console.error('Auditor: Access denied')}Step 5: Bulk Disclosure Management
Section titled “Step 5: Bulk Disclosure Management”Manage disclosures for multiple transactions:
interface TransactionWithDisclosures { transactionId: string timestamp: number encryptedForParties: Map<string, ReturnType<typeof encryptForViewing>>}
async function createBulkDisclosures( transactions: TransactionData[], parties: Record<string, any>) { const disclosures: TransactionWithDisclosures[] = []
for (const tx of transactions) { const txDisclosures = new Map()
// Encrypt for each party for (const [partyName, partyKey] of Object.entries(parties)) { const encrypted = encryptForViewing(tx, partyKey) txDisclosures.set(partyName, encrypted) }
disclosures.push({ transactionId: `tx-${Date.now()}-${Math.random().toString(36).slice(2)}`, timestamp: tx.timestamp, encryptedForParties: txDisclosures, }) }
return disclosures}
// Example usageconst transactions: TransactionData[] = [ { sender: '0xCompany', recipient: '0xVendor1', amount: '1000000000', timestamp: Math.floor(Date.now() / 1000), }, { sender: '0xCompany', recipient: '0xVendor2', amount: '2000000000', timestamp: Math.floor(Date.now() / 1000), },]
const bulkDisclosures = await createBulkDisclosures(transactions, parties)console.log(`Created disclosures for ${bulkDisclosures.length} transactions`)Complete Example
Section titled “Complete Example”Full implementation of multi-party disclosure system:
import { SIP, encryptForViewing, decryptWithViewing, type TransactionData,} from '@sip-protocol/sdk'
class MultiPartyDisclosure { private sip: SIP private parties: Map<string, any>
constructor() { this.sip = new SIP({ network: 'testnet', mode: 'demo' }) this.parties = new Map() }
// Add authorized party addParty(name: string, derivationPath: string, masterKey: any) { const partyKey = this.sip.deriveViewingKey(masterKey, derivationPath) this.parties.set(name, partyKey) console.log(`Added party: ${name} (${partyKey.path})`) }
// Encrypt transaction for all parties encryptForAll(tx: TransactionData) { const encrypted = new Map()
for (const [name, key] of this.parties) { encrypted.set(name, encryptForViewing(tx, key)) }
return { transactionId: `tx-${Date.now()}`, timestamp: tx.timestamp, encrypted, } }
// Decrypt for specific party decryptFor(partyName: string, encryptedData: any) { const partyKey = this.parties.get(partyName) if (!partyKey) { throw new Error(`Unknown party: ${partyName}`) }
return decryptWithViewing(encryptedData, partyKey) }
// List all parties listParties() { return Array.from(this.parties.keys()) }}
// Usageasync function demonstrateMultiPartyDisclosure() { const disclosure = new MultiPartyDisclosure()
// Set up master key const masterKey = disclosure['sip'].generateViewingKey('/m/44/501/0/disclosure')
// Add parties disclosure.addParty('Federal Tax Authority', 'tax/federal', masterKey) disclosure.addParty('External Auditor', 'audit/external', masterKey) disclosure.addParty('AML Compliance', 'compliance/aml', masterKey) disclosure.addParty('Legal Department', 'legal/general', masterKey)
// Transaction to disclose const transaction: TransactionData = { sender: '0xCompanyWallet', recipient: '0xPartnerWallet', amount: '10000000000', // 10 SOL timestamp: Math.floor(Date.now() / 1000), }
// Encrypt for all parties const record = disclosure.encryptForAll(transaction) console.log('\nTransaction encrypted for parties:', disclosure.listParties())
// Each party decrypts their copy console.log('\n--- Party Access Verification ---') for (const party of disclosure.listParties()) { try { const encrypted = record.encrypted.get(party) const decrypted = disclosure.decryptFor(party, encrypted) console.log(`${party}: ✓ Access granted`) console.log(` Amount: ${decrypted.amount}`) } catch (error) { console.log(`${party}: ✗ Access denied`) } }
return record}
demonstrateMultiPartyDisclosure() .then(() => console.log('\nMulti-party disclosure complete')) .catch(err => console.error('Error:', err))Common Pitfalls
Section titled “Common Pitfalls”Pitfall 1: Using Same Nonce for Multiple Parties
Section titled “Pitfall 1: Using Same Nonce for Multiple Parties”Problem: Reusing encryption result instead of encrypting separately.
// ❌ Wrong - sharing same encrypted dataconst encrypted = encryptForViewing(tx, parties.taxAuthority)const disclosures = { tax: encrypted, auditor: encrypted, // Same object!}// Now both parties use same nonce - security issueSolution: Encrypt separately for each party.
// ✅ Correct - separate encryption per partyconst disclosures = { tax: encryptForViewing(tx, parties.taxAuthority), auditor: encryptForViewing(tx, parties.auditor),}// Each has unique nonce and ciphertextPitfall 2: Not Tracking Which Party Can Access What
Section titled “Pitfall 2: Not Tracking Which Party Can Access What”Problem: Storing encrypted data without metadata.
// ❌ Wrong - can't tell which key decrypts thisawait db.store({ encrypted: ciphertext })// Later: which party is this for?Solution: Store metadata about authorized parties.
// ✅ Correct - track authorized partyawait db.store({ encrypted: ciphertext, viewingKeyHash: encrypted.viewingKeyHash, authorizedParty: 'Tax Authority', createdAt: Date.now(),})Pitfall 3: Over-Sharing with Too Many Parties
Section titled “Pitfall 3: Over-Sharing with Too Many Parties”Problem: Encrypting for everyone “just in case”.
// ❌ Wrong - unnecessary disclosureconst parties = [ 'tax', 'auditor', 'compliance', 'legal', 'hr', 'marketing', 'sales', 'support', 'intern']// Does the intern really need access?Solution: Follow principle of least privilege.
// ✅ Correct - only necessary partiesconst necessaryParties = ['tax', 'auditor', 'compliance']// Only those who need to knowPitfall 4: Not Implementing Revocation
Section titled “Pitfall 4: Not Implementing Revocation”Problem: No way to revoke access after disclosure.
// ❌ Wrong - once disclosed, can't revokeconst encrypted = encryptForViewing(tx, partyKey)await sendToParty(encrypted)// If party relationship ends, they still have accessSolution: Implement expiration and revocation.
// ✅ Correct - add expirationinterface TimedDisclosure { encrypted: any expiresAt: number revoked: boolean}
const disclosure: TimedDisclosure = { encrypted: encryptForViewing(tx, partyKey), expiresAt: Date.now() + (90 * 24 * 60 * 60 * 1000), // 90 days revoked: false,}
// Check before allowing accessif (disclosure.revoked || Date.now() > disclosure.expiresAt) { throw new Error('Disclosure expired or revoked')}Pitfall 5: Not Auditing Access
Section titled “Pitfall 5: Not Auditing Access”Problem: No log of who accessed what data.
// ❌ Wrong - no audit trailconst data = decryptWithViewing(encrypted, partyKey)// Who accessed this? When? For what purpose?Solution: Log all access attempts.
// ✅ Correct - audit all accessasync function auditedDecrypt( encrypted: any, partyKey: any, partyName: string, purpose: string) { await auditLog.record({ party: partyName, viewingKeyHash: encrypted.viewingKeyHash, timestamp: Date.now(), purpose, success: true, })
return decryptWithViewing(encrypted, partyKey)}Best Practices
Section titled “Best Practices”- Separate Encryptions: Always encrypt separately for each party
- Metadata: Track who can access what and why
- Least Privilege: Only disclose to parties that need to know
- Expiration: Set reasonable expiration times
- Audit Logs: Log all disclosure creation and access
- Revocation: Implement ability to revoke access
- Key Rotation: Rotate viewing keys periodically
Next Steps
Section titled “Next Steps”- Implement stealth address scanning for recipients
- Build compliance reporting workflows
- Handle batch transactions efficiently