@sip-protocol/sdk 0.1.0 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/stealth.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  isValidCompressedPublicKey,
30
30
  isValidPrivateKey,
31
31
  } from './validation'
32
+ import { secureWipe, secureWipeAll } from './secure-memory'
32
33
 
33
34
  /**
34
35
  * Generate a new stealth meta-address keypair
@@ -58,19 +59,28 @@ export function generateStealthMetaAddress(
58
59
  const spendingPrivateKey = randomBytes(32)
59
60
  const viewingPrivateKey = randomBytes(32)
60
61
 
61
- // Derive public keys
62
- const spendingKey = secp256k1.getPublicKey(spendingPrivateKey, true)
63
- const viewingKey = secp256k1.getPublicKey(viewingPrivateKey, true)
64
-
65
- return {
66
- metaAddress: {
67
- spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
68
- viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
69
- chain,
70
- label,
71
- },
72
- spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
73
- viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
62
+ try {
63
+ // Derive public keys
64
+ const spendingKey = secp256k1.getPublicKey(spendingPrivateKey, true)
65
+ const viewingKey = secp256k1.getPublicKey(viewingPrivateKey, true)
66
+
67
+ // Convert to hex strings before wiping buffers
68
+ const result = {
69
+ metaAddress: {
70
+ spendingKey: `0x${bytesToHex(spendingKey)}` as HexString,
71
+ viewingKey: `0x${bytesToHex(viewingKey)}` as HexString,
72
+ chain,
73
+ label,
74
+ },
75
+ spendingPrivateKey: `0x${bytesToHex(spendingPrivateKey)}` as HexString,
76
+ viewingPrivateKey: `0x${bytesToHex(viewingPrivateKey)}` as HexString,
77
+ }
78
+
79
+ return result
80
+ } finally {
81
+ // Securely wipe private key buffers
82
+ // Note: The hex strings returned to caller must be handled securely by them
83
+ secureWipeAll(spendingPrivateKey, viewingPrivateKey)
74
84
  }
75
85
  }
76
86
 
@@ -128,41 +138,47 @@ export function generateStealthAddress(
128
138
 
129
139
  // Generate ephemeral keypair
130
140
  const ephemeralPrivateKey = randomBytes(32)
131
- const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true)
132
-
133
- // Parse recipient's keys (remove 0x prefix)
134
- const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
135
- const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
136
-
137
- // Compute shared secret: S = r * P (ephemeral private * spending public)
138
- const sharedSecretPoint = secp256k1.getSharedSecret(
139
- ephemeralPrivateKey,
140
- spendingKeyBytes,
141
- )
142
141
 
143
- // Hash the shared secret for use as a scalar
144
- const sharedSecretHash = sha256(sharedSecretPoint)
142
+ try {
143
+ const ephemeralPublicKey = secp256k1.getPublicKey(ephemeralPrivateKey, true)
145
144
 
146
- // Compute stealth address: A = Q + hash(S)*G
147
- // First get hash(S)*G
148
- const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
145
+ // Parse recipient's keys (remove 0x prefix)
146
+ const spendingKeyBytes = hexToBytes(recipientMetaAddress.spendingKey.slice(2))
147
+ const viewingKeyBytes = hexToBytes(recipientMetaAddress.viewingKey.slice(2))
149
148
 
150
- // Then add to viewing key Q
151
- const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes)
152
- const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
153
- const stealthPoint = viewingKeyPoint.add(hashTimesGPoint)
154
- const stealthAddressBytes = stealthPoint.toRawBytes(true)
155
-
156
- // Compute view tag (first byte of hash for efficient scanning)
157
- const viewTag = sharedSecretHash[0]
149
+ // Compute shared secret: S = r * P (ephemeral private * spending public)
150
+ const sharedSecretPoint = secp256k1.getSharedSecret(
151
+ ephemeralPrivateKey,
152
+ spendingKeyBytes,
153
+ )
158
154
 
159
- return {
160
- stealthAddress: {
161
- address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
162
- ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
163
- viewTag,
164
- },
165
- sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
155
+ // Hash the shared secret for use as a scalar
156
+ const sharedSecretHash = sha256(sharedSecretPoint)
157
+
158
+ // Compute stealth address: A = Q + hash(S)*G
159
+ // First get hash(S)*G
160
+ const hashTimesG = secp256k1.getPublicKey(sharedSecretHash, true)
161
+
162
+ // Then add to viewing key Q
163
+ const viewingKeyPoint = secp256k1.ProjectivePoint.fromHex(viewingKeyBytes)
164
+ const hashTimesGPoint = secp256k1.ProjectivePoint.fromHex(hashTimesG)
165
+ const stealthPoint = viewingKeyPoint.add(hashTimesGPoint)
166
+ const stealthAddressBytes = stealthPoint.toRawBytes(true)
167
+
168
+ // Compute view tag (first byte of hash for efficient scanning)
169
+ const viewTag = sharedSecretHash[0]
170
+
171
+ return {
172
+ stealthAddress: {
173
+ address: `0x${bytesToHex(stealthAddressBytes)}` as HexString,
174
+ ephemeralPublicKey: `0x${bytesToHex(ephemeralPublicKey)}` as HexString,
175
+ viewTag,
176
+ },
177
+ sharedSecret: `0x${bytesToHex(sharedSecretHash)}` as HexString,
178
+ }
179
+ } finally {
180
+ // Securely wipe ephemeral private key
181
+ secureWipe(ephemeralPrivateKey)
166
182
  }
167
183
  }
168
184
 
@@ -242,28 +258,38 @@ export function deriveStealthPrivateKey(
242
258
  const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
243
259
  const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
244
260
 
245
- // Compute shared secret: S = p * R (spending private * ephemeral public)
246
- const sharedSecretPoint = secp256k1.getSharedSecret(
247
- spendingPrivBytes,
248
- ephemeralPubBytes,
249
- )
261
+ try {
262
+ // Compute shared secret: S = p * R (spending private * ephemeral public)
263
+ const sharedSecretPoint = secp256k1.getSharedSecret(
264
+ spendingPrivBytes,
265
+ ephemeralPubBytes,
266
+ )
250
267
 
251
- // Hash the shared secret
252
- const sharedSecretHash = sha256(sharedSecretPoint)
268
+ // Hash the shared secret
269
+ const sharedSecretHash = sha256(sharedSecretPoint)
253
270
 
254
- // Derive stealth private key: q + hash(S) mod n
255
- // Where q is the viewing private key
256
- const viewingScalar = bytesToBigInt(viewingPrivBytes)
257
- const hashScalar = bytesToBigInt(sharedSecretHash)
258
- const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
271
+ // Derive stealth private key: q + hash(S) mod n
272
+ // Where q is the viewing private key
273
+ const viewingScalar = bytesToBigInt(viewingPrivBytes)
274
+ const hashScalar = bytesToBigInt(sharedSecretHash)
275
+ const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
259
276
 
260
- // Convert back to bytes
261
- const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32)
277
+ // Convert back to bytes
278
+ const stealthPrivateKey = bigIntToBytes(stealthPrivateScalar, 32)
262
279
 
263
- return {
264
- stealthAddress: stealthAddress.address,
265
- ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
266
- privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
280
+ const result = {
281
+ stealthAddress: stealthAddress.address,
282
+ ephemeralPublicKey: stealthAddress.ephemeralPublicKey,
283
+ privateKey: `0x${bytesToHex(stealthPrivateKey)}` as HexString,
284
+ }
285
+
286
+ // Wipe derived key buffer after converting to hex
287
+ secureWipe(stealthPrivateKey)
288
+
289
+ return result
290
+ } finally {
291
+ // Securely wipe input private key buffers
292
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
267
293
  }
268
294
  }
269
295
 
@@ -305,33 +331,39 @@ export function checkStealthAddress(
305
331
  const viewingPrivBytes = hexToBytes(viewingPrivateKey.slice(2))
306
332
  const ephemeralPubBytes = hexToBytes(stealthAddress.ephemeralPublicKey.slice(2))
307
333
 
308
- // Quick check: compute shared secret and verify view tag first
309
- const sharedSecretPoint = secp256k1.getSharedSecret(
310
- spendingPrivBytes,
311
- ephemeralPubBytes,
312
- )
313
- const sharedSecretHash = sha256(sharedSecretPoint)
334
+ try {
335
+ // Quick check: compute shared secret and verify view tag first
336
+ const sharedSecretPoint = secp256k1.getSharedSecret(
337
+ spendingPrivBytes,
338
+ ephemeralPubBytes,
339
+ )
340
+ const sharedSecretHash = sha256(sharedSecretPoint)
314
341
 
315
- // View tag check (optimization - reject quickly if doesn't match)
316
- if (sharedSecretHash[0] !== stealthAddress.viewTag) {
317
- return false
318
- }
342
+ // View tag check (optimization - reject quickly if doesn't match)
343
+ if (sharedSecretHash[0] !== stealthAddress.viewTag) {
344
+ return false
345
+ }
346
+
347
+ // Full check: derive the expected stealth address
348
+ const viewingScalar = bytesToBigInt(viewingPrivBytes)
349
+ const hashScalar = bytesToBigInt(sharedSecretHash)
350
+ const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
319
351
 
320
- // Full check: derive the expected stealth address
321
- const viewingScalar = bytesToBigInt(viewingPrivBytes)
322
- const hashScalar = bytesToBigInt(sharedSecretHash)
323
- const stealthPrivateScalar = (viewingScalar + hashScalar) % secp256k1.CURVE.n
352
+ // Compute expected public key from derived private key
353
+ const derivedKeyBytes = bigIntToBytes(stealthPrivateScalar, 32)
354
+ const expectedPubKey = secp256k1.getPublicKey(derivedKeyBytes, true)
324
355
 
325
- // Compute expected public key from derived private key
326
- const expectedPubKey = secp256k1.getPublicKey(
327
- bigIntToBytes(stealthPrivateScalar, 32),
328
- true,
329
- )
356
+ // Wipe derived key immediately after use
357
+ secureWipe(derivedKeyBytes)
330
358
 
331
- // Compare with provided stealth address
332
- const providedAddress = hexToBytes(stealthAddress.address.slice(2))
359
+ // Compare with provided stealth address
360
+ const providedAddress = hexToBytes(stealthAddress.address.slice(2))
333
361
 
334
- return bytesToHex(expectedPubKey) === bytesToHex(providedAddress)
362
+ return bytesToHex(expectedPubKey) === bytesToHex(providedAddress)
363
+ } finally {
364
+ // Securely wipe input private key buffers
365
+ secureWipeAll(spendingPrivBytes, viewingPrivBytes)
366
+ }
335
367
  }
336
368
 
337
369
  /**
@@ -0,0 +1,43 @@
1
+ /**
2
+ * DAO Treasury Module for SIP Protocol
3
+ *
4
+ * Provides privacy-preserving treasury management with multi-sig support
5
+ * for DAOs and organizations.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { Treasury, getStablecoin } from '@sip-protocol/sdk'
10
+ *
11
+ * // Create a 2-of-3 multi-sig treasury
12
+ * const treasury = await Treasury.create({
13
+ * name: 'DAO Treasury',
14
+ * chain: 'ethereum',
15
+ * members: [
16
+ * { address: alice, publicKey: alicePub, role: 'owner', name: 'Alice' },
17
+ * { address: bob, publicKey: bobPub, role: 'signer', name: 'Bob' },
18
+ * { address: carol, publicKey: carolPub, role: 'signer', name: 'Carol' },
19
+ * ],
20
+ * signingThreshold: 2,
21
+ * })
22
+ *
23
+ * // Create a batch payment proposal for payroll
24
+ * const proposal = await treasury.createBatchProposal({
25
+ * title: 'November Payroll',
26
+ * token: getStablecoin('USDC', 'ethereum')!,
27
+ * recipients: [
28
+ * { address: emp1MetaAddr, amount: 5000_000000n, purpose: 'salary' },
29
+ * { address: emp2MetaAddr, amount: 4500_000000n, purpose: 'salary' },
30
+ * { address: emp3MetaAddr, amount: 6000_000000n, purpose: 'salary' },
31
+ * ],
32
+ * })
33
+ *
34
+ * // Collect signatures
35
+ * await treasury.signProposal(proposal.proposalId, alice, alicePrivKey)
36
+ * await treasury.signProposal(proposal.proposalId, bob, bobPrivKey)
37
+ *
38
+ * // Execute when approved
39
+ * const payments = await treasury.executeProposal(proposal.proposalId)
40
+ * ```
41
+ */
42
+
43
+ export { Treasury } from './treasury'