@orbinum/sdk 1.1.1 → 1.2.0

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/dist/index.mjs CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  LEAVES_PER_TREE,
16
16
  MAX_WORKERS,
17
17
  NoteBuilder,
18
+ OVK_BLOB_SIZE,
18
19
  PAIRWISE_EPH_WINDOW,
19
20
  SELF_EPH_WINDOW,
20
21
  WORKER_CRASHED,
@@ -38,6 +39,8 @@ import {
38
39
  createWorkerPool,
39
40
  decodeNoteDisclosureKey,
40
41
  decryptHintBatch,
42
+ deriveOutgoingCipherKey,
43
+ deriveOutgoingViewingKey,
41
44
  deriveOwnerPk,
42
45
  derivePairwiseEphSk,
43
46
  derivePairwiseSharedSecret,
@@ -59,18 +62,22 @@ import {
59
62
  isSpendable,
60
63
  isValidLeafIndex,
61
64
  leHexToBigint,
65
+ openOutgoingBlob,
62
66
  pairwiseEphWindow,
67
+ randomOutgoingBlob,
63
68
  recoverOwnerPkPoint,
64
69
  scalarToHex,
65
70
  scanAbortError,
71
+ sealOutgoingBlob,
66
72
  selectNotes,
67
73
  selfEphWindow,
68
74
  serializeMemo,
69
75
  toHex,
70
76
  treeIdOf,
71
77
  tryDecryptNote,
72
- tryDecryptNoteVerbose
73
- } from "./chunk-Y6LNYJAJ.mjs";
78
+ tryDecryptNoteVerbose,
79
+ tryRecoverOutgoing
80
+ } from "./chunk-A2ZRMEYW.mjs";
74
81
 
75
82
  // src/foundation/encoding/base64.ts
76
83
  var ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -365,6 +372,101 @@ function addressToFieldElement(address) {
365
372
  return bytesToBigintLE(bytes) % BN254_R;
366
373
  }
367
374
 
375
+ // src/protocol/memo/PaymentSlip.ts
376
+ import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
377
+ import { randomBytes } from "@noble/ciphers/utils.js";
378
+ import { hkdf } from "@noble/hashes/hkdf.js";
379
+ import { sha256 } from "@noble/hashes/sha2.js";
380
+ import { packPoint, unpackPoint } from "@zk-kit/baby-jubjub";
381
+ var SLIP_DOMAIN = new TextEncoder().encode("orbinum-payment-slip-v1");
382
+ var NONCE_PREFIX = new TextEncoder().encode("SLP1");
383
+ var EPH_PK_SIZE = 32;
384
+ var NONCE_SUFFIX_SIZE = 8;
385
+ function deriveSlipKey(sharedSecret) {
386
+ return hkdf(sha256, sharedSecret, void 0, SLIP_DOMAIN, 32);
387
+ }
388
+ function buildNonce(suffix) {
389
+ const nonce = new Uint8Array(12);
390
+ nonce.set(NONCE_PREFIX, 0);
391
+ nonce.set(suffix, 4);
392
+ return nonce;
393
+ }
394
+ function sealPaymentSlip(recipientIvkPacked, fields) {
395
+ if (recipientIvkPacked.length !== 32) {
396
+ throw new Error(
397
+ `PaymentSlip: recipientIvk must be 32 bytes, got ${recipientIvkPacked.length}`
398
+ );
399
+ }
400
+ const ivkPoint = unpackPoint(bytesToBigintLE(recipientIvkPacked));
401
+ if (!ivkPoint) throw new Error("PaymentSlip: invalid recipient viewing public key");
402
+ const ephSkScalar = bytesToBjjScalar(randomBytes(32));
403
+ const ephPkPacked = bigintTo32Le(packPoint(fastMulBase(ephSkScalar)));
404
+ const sharedPoint = fastMulPoint(ivkPoint, ephSkScalar);
405
+ const sharedSecret = bigintTo32Le(sharedPoint[0]);
406
+ const slipKey = deriveSlipKey(sharedSecret);
407
+ const suffix = randomBytes(NONCE_SUFFIX_SIZE);
408
+ const cipher = chacha20poly1305(slipKey, buildNonce(suffix));
409
+ const plaintext = new TextEncoder().encode(JSON.stringify(fields));
410
+ const sealed = cipher.encrypt(plaintext);
411
+ const envelope = new Uint8Array(EPH_PK_SIZE + NONCE_SUFFIX_SIZE + sealed.length);
412
+ envelope.set(ephPkPacked, 0);
413
+ envelope.set(suffix, EPH_PK_SIZE);
414
+ envelope.set(sealed, EPH_PK_SIZE + NONCE_SUFFIX_SIZE);
415
+ return envelope;
416
+ }
417
+ function openPaymentSlip(recipientIvsk, envelope) {
418
+ if (envelope.length < EPH_PK_SIZE + NONCE_SUFFIX_SIZE + 16) return null;
419
+ try {
420
+ const ephPkPacked = envelope.subarray(0, EPH_PK_SIZE);
421
+ const ephPkPoint = unpackPoint(bytesToBigintLE(ephPkPacked));
422
+ if (!ephPkPoint) return null;
423
+ const ivskScalar = bytesToBjjScalar(recipientIvsk);
424
+ const sharedSecret = bigintTo32Le(fastMulPoint(ephPkPoint, ivskScalar)[0]);
425
+ const slipKey = deriveSlipKey(sharedSecret);
426
+ const suffix = envelope.subarray(EPH_PK_SIZE, EPH_PK_SIZE + NONCE_SUFFIX_SIZE);
427
+ const sealed = envelope.subarray(EPH_PK_SIZE + NONCE_SUFFIX_SIZE);
428
+ const cipher = chacha20poly1305(slipKey, buildNonce(suffix));
429
+ const plaintext = cipher.decrypt(sealed);
430
+ const fields = JSON.parse(new TextDecoder().decode(plaintext));
431
+ if (typeof fields.commitmentHex !== "string" || typeof fields.encryptedMemo !== "string") {
432
+ return null;
433
+ }
434
+ return fields;
435
+ } catch {
436
+ return null;
437
+ }
438
+ }
439
+ var PAYMENT_SLIP_SCHEME = "orbslip1:";
440
+ function slipChecksum(payload) {
441
+ const digest = sha256(new TextEncoder().encode(PAYMENT_SLIP_SCHEME + payload));
442
+ return toHex(digest.slice(0, 4)).slice(2);
443
+ }
444
+ function encodePaymentSlip(envelope) {
445
+ const payload = base64UrlEncode(toHex(envelope));
446
+ return `${PAYMENT_SLIP_SCHEME}${payload}:${slipChecksum(payload)}`;
447
+ }
448
+ function decodePaymentSlip(text) {
449
+ if (!text.startsWith(PAYMENT_SLIP_SCHEME)) return null;
450
+ const rest = text.slice(PAYMENT_SLIP_SCHEME.length);
451
+ const sep = rest.lastIndexOf(":");
452
+ if (sep < 0) return null;
453
+ const payload = rest.slice(0, sep);
454
+ const checksum = rest.slice(sep + 1);
455
+ if (slipChecksum(payload) !== checksum) return null;
456
+ try {
457
+ const hex = base64UrlDecode(payload);
458
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
459
+ if (clean.length % 2 !== 0) return null;
460
+ const bytes = new Uint8Array(clean.length / 2);
461
+ for (let i = 0; i < bytes.length; i++) {
462
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
463
+ }
464
+ return bytes;
465
+ } catch {
466
+ return null;
467
+ }
468
+ }
469
+
368
470
  // src/chain/evm/precompiles/addresses.ts
369
471
  var PRECOMPILE_ADDR = {
370
472
  // ── Ethereum standard (EIP) ─────────────────────────────────────────────
@@ -463,10 +565,10 @@ ${canonicalAccountId(address)}`;
463
565
  }
464
566
 
465
567
  // src/protocol/keys/PrivacyKeyManager.ts
466
- import { sha256 } from "@noble/hashes/sha2.js";
568
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
467
569
  function privacyAddressChecksum(ownerPkHex, ivkHex) {
468
570
  const body = `orbpriv2:${ownerPkHex}:${ivkHex}`;
469
- const digest = sha256(new TextEncoder().encode(body));
571
+ const digest = sha2562(new TextEncoder().encode(body));
470
572
  return toHex(digest.slice(0, 4)).slice(2);
471
573
  }
472
574
  var PrivacyKeyManager = class {
@@ -475,7 +577,8 @@ var PrivacyKeyManager = class {
475
577
  masterBytes: null,
476
578
  viewingSecretKey: null,
477
579
  viewingPublicKeyPacked: null,
478
- ownerPk: null
580
+ ownerPk: null,
581
+ outgoingViewingKey: null
479
582
  };
480
583
  /**
481
584
  * Load a spending key and its corresponding master bytes into the in-memory session.
@@ -490,12 +593,14 @@ var PrivacyKeyManager = class {
490
593
  const viewingSecretKey = deriveViewingSecretKey(spendingKey);
491
594
  const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
492
595
  const ownerPk = deriveOwnerPk(spendingKey);
596
+ const outgoingViewingKey = deriveOutgoingViewingKey(masterBytes);
493
597
  this._state = {
494
598
  spendingKey,
495
599
  masterBytes,
496
600
  viewingSecretKey,
497
601
  viewingPublicKeyPacked,
498
- ownerPk
602
+ ownerPk,
603
+ outgoingViewingKey
499
604
  };
500
605
  }
501
606
  /** Clear all key material from memory. Call on vault lock / sign-out. */
@@ -505,7 +610,8 @@ var PrivacyKeyManager = class {
505
610
  masterBytes: null,
506
611
  viewingSecretKey: null,
507
612
  viewingPublicKeyPacked: null,
508
- ownerPk: null
613
+ ownerPk: null,
614
+ outgoingViewingKey: null
509
615
  };
510
616
  }
511
617
  /** Returns true if a spending key has been loaded. */
@@ -550,6 +656,16 @@ var PrivacyKeyManager = class {
550
656
  }
551
657
  return this._state.ownerPk;
552
658
  }
659
+ /**
660
+ * Returns the 32-byte outgoing viewing key (ovk). Throws if not loaded.
661
+ * Used to seal/open the outgoing blob that lets the sender recover a transfer.
662
+ */
663
+ getOutgoingViewingKey() {
664
+ if (this._state.outgoingViewingKey === null) {
665
+ throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
666
+ }
667
+ return this._state.outgoingViewingKey;
668
+ }
553
669
  /** Returns the spending key as a 32-byte little-endian Uint8Array. Throws if not loaded. */
554
670
  getSpendingKeyBytes() {
555
671
  return bigintTo32Le(this.getSpendingKey());
@@ -651,8 +767,8 @@ var PrivacyKeyManager = class {
651
767
  };
652
768
 
653
769
  // src/protocol/keys/spendingKeyDerivation.ts
654
- import { hkdf } from "@noble/hashes/hkdf.js";
655
- import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
770
+ import { hkdf as hkdf2 } from "@noble/hashes/hkdf.js";
771
+ import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
656
772
  var MIN_SIGNATURE_BYTES = 32;
657
773
  var MIN_DISTINCT_BYTES = 8;
658
774
  function assertUsableSignature(sigBytes) {
@@ -674,7 +790,7 @@ async function deriveMasterKeyBytes(signatureHex, chainId, address) {
674
790
  const info = new TextEncoder().encode(
675
791
  `orbinum-sk-${KEY_VERSION}:${chainId}:${canonicalAccountId(address)}`
676
792
  );
677
- return hkdf(sha2562, sigBytes, new Uint8Array(0), info, 32);
793
+ return hkdf2(sha2563, sigBytes, new Uint8Array(0), info, 32);
678
794
  }
679
795
  async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
680
796
  return deriveSpendingKeyFromMaster(await deriveMasterKeyBytes(signatureHex, chainId, address));
@@ -686,7 +802,7 @@ import {
686
802
  generateProof,
687
803
  WebArtifactProvider
688
804
  } from "@orbinum/proof-generator";
689
- import { randomBytes } from "@noble/ciphers/utils.js";
805
+ import { randomBytes as randomBytes2 } from "@noble/ciphers/utils.js";
690
806
  import { mulPointEscalar, Base8 } from "@zk-kit/baby-jubjub";
691
807
  import { poseidon4 } from "poseidon-lite";
692
808
 
@@ -720,7 +836,7 @@ async function generateUnshieldProof(inputs, options = {}) {
720
836
  throw new Error("changeValue must be >= 0.");
721
837
  }
722
838
  const changeOwnerPubkey = inputs.changeOwnerPubkey ?? mulPointEscalar(Base8, inputs.spendingKey)[0];
723
- const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE(randomBytes(32)) : 0n);
839
+ const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE(randomBytes2(32)) : 0n);
724
840
  const changeCommitment = changeValue > 0n ? poseidon4([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
725
841
  const circuitInputs = {
726
842
  merkle_root: leHexToBigint(inputs.merkleRoot).toString(),
@@ -5300,6 +5416,22 @@ function isEntry2(value) {
5300
5416
  return true;
5301
5417
  }
5302
5418
 
5419
+ // src/wallet/ops/notes/paymentSlipImport.ts
5420
+ function importPaymentSlip(slip, keys) {
5421
+ const envelope = typeof slip === "string" ? decodePaymentSlip(slip) : slip;
5422
+ if (!envelope) return null;
5423
+ const fields = openPaymentSlip(keys.viewingSecretKey, envelope);
5424
+ if (!fields) return null;
5425
+ const commitment = {
5426
+ commitmentHex: fields.commitmentHex,
5427
+ leafIndex: fields.leafIndex ?? -1,
5428
+ encryptedMemo: fields.encryptedMemo
5429
+ };
5430
+ const note = tryDecryptNote(commitment, keys.viewingSecretKey, keys.spendingKey, keys.ownerPk);
5431
+ if (!note) return null;
5432
+ return fields.txHash ? stampCreatedTxHash(note, fields.txHash) : note;
5433
+ }
5434
+
5303
5435
  // src/wallet/ops/spend/transfer.ts
5304
5436
  import { CircuitType as CircuitType5 } from "@orbinum/proof-generator";
5305
5437
 
@@ -5509,6 +5641,18 @@ async function transferNotes(deps, params, onProgress) {
5509
5641
  await deps.vault.save(stampCreatedTxHash(selfNote, txResult.txHash, txKind));
5510
5642
  }
5511
5643
  }
5644
+ const isSelf = deps.selfOwnerPk !== null && recipientPk === deps.selfOwnerPk;
5645
+ if (recipientViewingPublicKey !== void 0 && !isSelf) {
5646
+ try {
5647
+ const envelope = sealPaymentSlip(recipientViewingPublicKey, {
5648
+ commitmentHex: recipientNote.commitmentHex,
5649
+ encryptedMemo: toHex(Uint8Array.from(recipientNote.memo)),
5650
+ ...txResult.txHash ? { txHash: txResult.txHash } : {}
5651
+ });
5652
+ return { ...txResult, paymentSlip: encodePaymentSlip(envelope) };
5653
+ } catch {
5654
+ }
5655
+ }
5512
5656
  }
5513
5657
  return txResult;
5514
5658
  }
@@ -6114,11 +6258,13 @@ export {
6114
6258
  NOTE_BIGINT_FIELDS,
6115
6259
  NOTE_TRANSFER_URI_SCHEME,
6116
6260
  NoteBuilder,
6261
+ OVK_BLOB_SIZE,
6117
6262
  OrbinumClient,
6118
6263
  OrbinumClientProvider,
6119
6264
  OrbinumWallet,
6120
6265
  PAGE_SIZE,
6121
6266
  PAIRWISE_EPH_WINDOW,
6267
+ PAYMENT_SLIP_SCHEME,
6122
6268
  PRECOMPILE_ADDR,
6123
6269
  PrivacyKeyManager,
6124
6270
  PrivacyModule,
@@ -6189,11 +6335,14 @@ export {
6189
6335
  decodeNoteBackup,
6190
6336
  decodeNoteDisclosureKey,
6191
6337
  decodeNoteTransferPage,
6338
+ decodePaymentSlip,
6192
6339
  decodePrecompileCalldata,
6193
6340
  decryptHintBatch,
6194
6341
  decryptJson,
6195
6342
  decryptNoteRecord,
6196
6343
  deriveMasterKeyBytes,
6344
+ deriveOutgoingCipherKey,
6345
+ deriveOutgoingViewingKey,
6197
6346
  deriveOwnerPk,
6198
6347
  derivePairwiseEphSk,
6199
6348
  derivePairwiseSharedSecret,
@@ -6212,6 +6361,7 @@ export {
6212
6361
  detectCommitmentMismatch,
6213
6362
  encodeNoteBackup,
6214
6363
  encodeNoteTransferPages,
6364
+ encodePaymentSlip,
6215
6365
  encryptJson,
6216
6366
  encryptNote,
6217
6367
  ensureCreatedAt,
@@ -6248,6 +6398,7 @@ export {
6248
6398
  implicitSubstrateToEvm,
6249
6399
  importDeviceKey,
6250
6400
  importNotesFromBackup,
6401
+ importPaymentSlip,
6251
6402
  isAbortError,
6252
6403
  isAlreadySpentError,
6253
6404
  isConnectionLossError,
@@ -6277,6 +6428,8 @@ export {
6277
6428
  noteSpentTxHash,
6278
6429
  noteToTransferEntry,
6279
6430
  noteTxKind,
6431
+ openOutgoingBlob,
6432
+ openPaymentSlip,
6280
6433
  pairwiseEphWindow,
6281
6434
  palletErrorKind,
6282
6435
  parseAmount,
@@ -6286,6 +6439,7 @@ export {
6286
6439
  planTransfer,
6287
6440
  planUnshield,
6288
6441
  randomBlinding,
6442
+ randomOutgoingBlob,
6289
6443
  reconstructOutgoingTxRecords,
6290
6444
  recoverOwnerPkPoint,
6291
6445
  recoverSelfStealthNote,
@@ -6301,6 +6455,8 @@ export {
6301
6455
  runScan,
6302
6456
  scalarToHex,
6303
6457
  scanAbortError,
6458
+ sealOutgoingBlob,
6459
+ sealPaymentSlip,
6304
6460
  selectGhosts,
6305
6461
  selectNotes,
6306
6462
  selfEphWindow,
@@ -6324,6 +6480,7 @@ export {
6324
6480
  truncateMiddle,
6325
6481
  tryDecryptNote,
6326
6482
  tryDecryptNoteVerbose,
6483
+ tryRecoverOutgoing,
6327
6484
  txLandedAfterError,
6328
6485
  u128,
6329
6486
  u64,
@@ -1 +1 @@
1
- export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-JYVjYJtf.mjs';
1
+ export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-V5Z9igEN.mjs';
@@ -1 +1 @@
1
- export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-JYVjYJtf.js';
1
+ export { c as DECRYPT_YIELD_EVERY, d as DecryptBatchResult, a as DecryptPool, e as DecryptRequest, E as EMPTY_BATCH_RESULT, K as KnownEphEntry, f as KnownEphWindow, M as MAX_WORKERS, g as MatchSource, P as PAIRWISE_EPH_WINDOW, i as SELF_EPH_WINDOW, b as ScanKeys, W as WORKER_CRASHED, j as WorkerFactory, k as WorkerLike, l as WorkerMessage, m as clearKnownEphWindow, n as createDecryptPool, o as createMainThreadPool, p as createWorkerPool, q as decryptHintBatch, r as getKnownEphWindow } from '../../index-V5Z9igEN.js';
@@ -570,6 +570,7 @@ var import_hkdf2 = require("@noble/hashes/hkdf.js");
570
570
  var import_sha25 = require("@noble/hashes/sha2.js");
571
571
  var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
572
572
  var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
573
+ var OVK_DOMAIN = new TextEncoder().encode("orbinum-ovk-v1");
573
574
  function deriveViewingPublicKey(ivsk) {
574
575
  const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
575
576
  const ivkPoint = fastMulBase(ivskScalar);
@@ -11,7 +11,7 @@ import {
11
11
  createWorkerPool,
12
12
  decryptHintBatch,
13
13
  getKnownEphWindow
14
- } from "../../chunk-Y6LNYJAJ.mjs";
14
+ } from "../../chunk-A2ZRMEYW.mjs";
15
15
  export {
16
16
  DECRYPT_YIELD_EVERY,
17
17
  EMPTY_BATCH_RESULT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orbinum/sdk",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Official TypeScript SDK for Orbinum.",
5
5
  "author": "Orbinum",
6
6
  "license": "MIT",