@orbinum/sdk 1.1.1 → 1.3.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/{chunk-Y6LNYJAJ.mjs → chunk-A2ZRMEYW.mjs} +147 -13
- package/dist/{index-JYVjYJtf.d.mts → index-V5Z9igEN.d.mts} +41 -1
- package/dist/{index-JYVjYJtf.d.ts → index-V5Z9igEN.d.ts} +41 -1
- package/dist/index.d.mts +224 -10
- package/dist/index.d.ts +224 -10
- package/dist/index.js +485 -130
- package/dist/index.mjs +320 -98
- package/dist/wallet/worker/index.d.mts +1 -1
- package/dist/wallet/worker/index.d.ts +1 -1
- package/dist/wallet/worker/index.js +1 -0
- package/dist/wallet/worker/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -49,11 +49,13 @@ __export(index_exports, {
|
|
|
49
49
|
NOTE_BIGINT_FIELDS: () => NOTE_BIGINT_FIELDS,
|
|
50
50
|
NOTE_TRANSFER_URI_SCHEME: () => NOTE_TRANSFER_URI_SCHEME,
|
|
51
51
|
NoteBuilder: () => NoteBuilder,
|
|
52
|
+
OVK_BLOB_SIZE: () => OVK_BLOB_SIZE,
|
|
52
53
|
OrbinumClient: () => OrbinumClient,
|
|
53
54
|
OrbinumClientProvider: () => OrbinumClientProvider,
|
|
54
55
|
OrbinumWallet: () => OrbinumWallet,
|
|
55
56
|
PAGE_SIZE: () => PAGE_SIZE,
|
|
56
57
|
PAIRWISE_EPH_WINDOW: () => PAIRWISE_EPH_WINDOW,
|
|
58
|
+
PAYMENT_SLIP_SCHEME: () => PAYMENT_SLIP_SCHEME,
|
|
57
59
|
PRECOMPILE_ADDR: () => PRECOMPILE_ADDR,
|
|
58
60
|
PrivacyKeyManager: () => PrivacyKeyManager,
|
|
59
61
|
PrivacyModule: () => PrivacyModule,
|
|
@@ -124,11 +126,14 @@ __export(index_exports, {
|
|
|
124
126
|
decodeNoteBackup: () => decodeNoteBackup,
|
|
125
127
|
decodeNoteDisclosureKey: () => decodeNoteDisclosureKey,
|
|
126
128
|
decodeNoteTransferPage: () => decodeNoteTransferPage,
|
|
129
|
+
decodePaymentSlip: () => decodePaymentSlip,
|
|
127
130
|
decodePrecompileCalldata: () => decodePrecompileCalldata,
|
|
128
131
|
decryptHintBatch: () => decryptHintBatch,
|
|
129
132
|
decryptJson: () => decryptJson,
|
|
130
133
|
decryptNoteRecord: () => decryptNoteRecord,
|
|
131
134
|
deriveMasterKeyBytes: () => deriveMasterKeyBytes,
|
|
135
|
+
deriveOutgoingCipherKey: () => deriveOutgoingCipherKey,
|
|
136
|
+
deriveOutgoingViewingKey: () => deriveOutgoingViewingKey,
|
|
132
137
|
deriveOwnerPk: () => deriveOwnerPk,
|
|
133
138
|
derivePairwiseEphSk: () => derivePairwiseEphSk,
|
|
134
139
|
derivePairwiseSharedSecret: () => derivePairwiseSharedSecret,
|
|
@@ -147,6 +152,7 @@ __export(index_exports, {
|
|
|
147
152
|
detectCommitmentMismatch: () => detectCommitmentMismatch,
|
|
148
153
|
encodeNoteBackup: () => encodeNoteBackup,
|
|
149
154
|
encodeNoteTransferPages: () => encodeNoteTransferPages,
|
|
155
|
+
encodePaymentSlip: () => encodePaymentSlip,
|
|
150
156
|
encryptJson: () => encryptJson,
|
|
151
157
|
encryptNote: () => encryptNote,
|
|
152
158
|
ensureCreatedAt: () => ensureCreatedAt,
|
|
@@ -183,6 +189,7 @@ __export(index_exports, {
|
|
|
183
189
|
implicitSubstrateToEvm: () => implicitSubstrateToEvm,
|
|
184
190
|
importDeviceKey: () => importDeviceKey,
|
|
185
191
|
importNotesFromBackup: () => importNotesFromBackup,
|
|
192
|
+
importPaymentSlip: () => importPaymentSlip,
|
|
186
193
|
isAbortError: () => isAbortError,
|
|
187
194
|
isAlreadySpentError: () => isAlreadySpentError,
|
|
188
195
|
isConnectionLossError: () => isConnectionLossError,
|
|
@@ -212,6 +219,8 @@ __export(index_exports, {
|
|
|
212
219
|
noteSpentTxHash: () => noteSpentTxHash,
|
|
213
220
|
noteToTransferEntry: () => noteToTransferEntry,
|
|
214
221
|
noteTxKind: () => noteTxKind,
|
|
222
|
+
openOutgoingBlob: () => openOutgoingBlob,
|
|
223
|
+
openPaymentSlip: () => openPaymentSlip,
|
|
215
224
|
pairwiseEphWindow: () => pairwiseEphWindow,
|
|
216
225
|
palletErrorKind: () => palletErrorKind,
|
|
217
226
|
parseAmount: () => parseAmount,
|
|
@@ -221,6 +230,7 @@ __export(index_exports, {
|
|
|
221
230
|
planTransfer: () => planTransfer,
|
|
222
231
|
planUnshield: () => planUnshield,
|
|
223
232
|
randomBlinding: () => randomBlinding,
|
|
233
|
+
randomOutgoingBlob: () => randomOutgoingBlob,
|
|
224
234
|
reconstructOutgoingTxRecords: () => reconstructOutgoingTxRecords,
|
|
225
235
|
recoverOwnerPkPoint: () => recoverOwnerPkPoint,
|
|
226
236
|
recoverSelfStealthNote: () => recoverSelfStealthNote,
|
|
@@ -236,6 +246,8 @@ __export(index_exports, {
|
|
|
236
246
|
runScan: () => runScan,
|
|
237
247
|
scalarToHex: () => scalarToHex,
|
|
238
248
|
scanAbortError: () => scanAbortError,
|
|
249
|
+
sealOutgoingBlob: () => sealOutgoingBlob,
|
|
250
|
+
sealPaymentSlip: () => sealPaymentSlip,
|
|
239
251
|
selectGhosts: () => selectGhosts,
|
|
240
252
|
selectNotes: () => selectNotes,
|
|
241
253
|
selfEphWindow: () => selfEphWindow,
|
|
@@ -259,6 +271,7 @@ __export(index_exports, {
|
|
|
259
271
|
truncateMiddle: () => truncateMiddle,
|
|
260
272
|
tryDecryptNote: () => tryDecryptNote,
|
|
261
273
|
tryDecryptNoteVerbose: () => tryDecryptNoteVerbose,
|
|
274
|
+
tryRecoverOutgoing: () => tryRecoverOutgoing,
|
|
262
275
|
txLandedAfterError: () => txLandedAfterError,
|
|
263
276
|
u128: () => import_substrate_bindings3.u128,
|
|
264
277
|
u64: () => import_substrate_bindings3.u64,
|
|
@@ -1037,12 +1050,168 @@ var EncryptedMemo = {
|
|
|
1037
1050
|
}
|
|
1038
1051
|
};
|
|
1039
1052
|
|
|
1040
|
-
// src/protocol/
|
|
1053
|
+
// src/protocol/memo/OutgoingBlob.ts
|
|
1054
|
+
var import_chacha2 = require("@noble/ciphers/chacha.js");
|
|
1055
|
+
var import_utils2 = require("@noble/ciphers/utils.js");
|
|
1056
|
+
var import_hkdf2 = require("@noble/hashes/hkdf.js");
|
|
1041
1057
|
var import_sha23 = require("@noble/hashes/sha2.js");
|
|
1058
|
+
var OCK_DOMAIN = new TextEncoder().encode("orbinum-outgoing-cipher-v1");
|
|
1059
|
+
var NONCE_PREFIX = new TextEncoder().encode("OVK1");
|
|
1060
|
+
var NONCE_SUFFIX_SIZE = 8;
|
|
1061
|
+
var SHARED_SECRET_SIZE = 32;
|
|
1062
|
+
var MAC_SIZE = 16;
|
|
1063
|
+
var OVK_BLOB_SIZE = NONCE_SUFFIX_SIZE + SHARED_SECRET_SIZE + MAC_SIZE;
|
|
1064
|
+
function deriveOutgoingCipherKey(ovk, commitmentBytes, ephPkBytes) {
|
|
1065
|
+
if (commitmentBytes.length !== 32) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
`OutgoingBlob: commitmentBytes must be 32 bytes, got ${commitmentBytes.length}`
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
if (ephPkBytes.length !== 32) {
|
|
1071
|
+
throw new Error(`OutgoingBlob: ephPkBytes must be 32 bytes, got ${ephPkBytes.length}`);
|
|
1072
|
+
}
|
|
1073
|
+
const salt = new Uint8Array(64);
|
|
1074
|
+
salt.set(commitmentBytes, 0);
|
|
1075
|
+
salt.set(ephPkBytes, 32);
|
|
1076
|
+
return (0, import_hkdf2.hkdf)(import_sha23.sha256, ovk, salt, OCK_DOMAIN, 32);
|
|
1077
|
+
}
|
|
1078
|
+
function buildNonce(suffix) {
|
|
1079
|
+
const nonce = new Uint8Array(12);
|
|
1080
|
+
nonce.set(NONCE_PREFIX, 0);
|
|
1081
|
+
nonce.set(suffix, 4);
|
|
1082
|
+
return nonce;
|
|
1083
|
+
}
|
|
1084
|
+
function sealOutgoingBlob(ovk, sharedSecret, commitmentBytes, ephPkBytes) {
|
|
1085
|
+
if (sharedSecret.length !== SHARED_SECRET_SIZE) {
|
|
1086
|
+
throw new Error(`OutgoingBlob: sharedSecret must be 32 bytes, got ${sharedSecret.length}`);
|
|
1087
|
+
}
|
|
1088
|
+
const ock = deriveOutgoingCipherKey(ovk, commitmentBytes, ephPkBytes);
|
|
1089
|
+
const suffix = (0, import_utils2.randomBytes)(NONCE_SUFFIX_SIZE);
|
|
1090
|
+
const cipher = (0, import_chacha2.chacha20poly1305)(ock, buildNonce(suffix));
|
|
1091
|
+
const sealed = cipher.encrypt(sharedSecret);
|
|
1092
|
+
const blob = new Uint8Array(OVK_BLOB_SIZE);
|
|
1093
|
+
blob.set(suffix, 0);
|
|
1094
|
+
blob.set(sealed, NONCE_SUFFIX_SIZE);
|
|
1095
|
+
return blob;
|
|
1096
|
+
}
|
|
1097
|
+
function openOutgoingBlob(ovk, blob, commitmentBytes, ephPkBytes) {
|
|
1098
|
+
if (blob.length !== OVK_BLOB_SIZE) return null;
|
|
1099
|
+
if (commitmentBytes.length !== 32 || ephPkBytes.length !== 32) return null;
|
|
1100
|
+
try {
|
|
1101
|
+
const ock = deriveOutgoingCipherKey(ovk, commitmentBytes, ephPkBytes);
|
|
1102
|
+
const suffix = blob.subarray(0, NONCE_SUFFIX_SIZE);
|
|
1103
|
+
const sealed = blob.subarray(NONCE_SUFFIX_SIZE);
|
|
1104
|
+
const cipher = (0, import_chacha2.chacha20poly1305)(ock, buildNonce(suffix));
|
|
1105
|
+
return cipher.decrypt(sealed);
|
|
1106
|
+
} catch {
|
|
1107
|
+
return null;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function randomOutgoingBlob() {
|
|
1111
|
+
return (0, import_utils2.randomBytes)(OVK_BLOB_SIZE);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// src/protocol/memo/PaymentSlip.ts
|
|
1115
|
+
var import_chacha3 = require("@noble/ciphers/chacha.js");
|
|
1116
|
+
var import_utils3 = require("@noble/ciphers/utils.js");
|
|
1117
|
+
var import_hkdf3 = require("@noble/hashes/hkdf.js");
|
|
1118
|
+
var import_sha24 = require("@noble/hashes/sha2.js");
|
|
1042
1119
|
var import_baby_jubjub4 = require("@zk-kit/baby-jubjub");
|
|
1120
|
+
var SLIP_DOMAIN = new TextEncoder().encode("orbinum-payment-slip-v1");
|
|
1121
|
+
var NONCE_PREFIX2 = new TextEncoder().encode("SLP1");
|
|
1122
|
+
var EPH_PK_SIZE2 = 32;
|
|
1123
|
+
var NONCE_SUFFIX_SIZE2 = 8;
|
|
1124
|
+
function deriveSlipKey(sharedSecret) {
|
|
1125
|
+
return (0, import_hkdf3.hkdf)(import_sha24.sha256, sharedSecret, void 0, SLIP_DOMAIN, 32);
|
|
1126
|
+
}
|
|
1127
|
+
function buildNonce2(suffix) {
|
|
1128
|
+
const nonce = new Uint8Array(12);
|
|
1129
|
+
nonce.set(NONCE_PREFIX2, 0);
|
|
1130
|
+
nonce.set(suffix, 4);
|
|
1131
|
+
return nonce;
|
|
1132
|
+
}
|
|
1133
|
+
function sealPaymentSlip(recipientIvkPacked, fields) {
|
|
1134
|
+
if (recipientIvkPacked.length !== 32) {
|
|
1135
|
+
throw new Error(
|
|
1136
|
+
`PaymentSlip: recipientIvk must be 32 bytes, got ${recipientIvkPacked.length}`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
const ivkPoint = (0, import_baby_jubjub4.unpackPoint)(bytesToBigintLE(recipientIvkPacked));
|
|
1140
|
+
if (!ivkPoint) throw new Error("PaymentSlip: invalid recipient viewing public key");
|
|
1141
|
+
const ephSkScalar = bytesToBjjScalar((0, import_utils3.randomBytes)(32));
|
|
1142
|
+
const ephPkPacked = bigintTo32Le((0, import_baby_jubjub4.packPoint)(fastMulBase(ephSkScalar)));
|
|
1143
|
+
const sharedPoint = fastMulPoint(ivkPoint, ephSkScalar);
|
|
1144
|
+
const sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1145
|
+
const slipKey = deriveSlipKey(sharedSecret);
|
|
1146
|
+
const suffix = (0, import_utils3.randomBytes)(NONCE_SUFFIX_SIZE2);
|
|
1147
|
+
const cipher = (0, import_chacha3.chacha20poly1305)(slipKey, buildNonce2(suffix));
|
|
1148
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(fields));
|
|
1149
|
+
const sealed = cipher.encrypt(plaintext);
|
|
1150
|
+
const envelope = new Uint8Array(EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2 + sealed.length);
|
|
1151
|
+
envelope.set(ephPkPacked, 0);
|
|
1152
|
+
envelope.set(suffix, EPH_PK_SIZE2);
|
|
1153
|
+
envelope.set(sealed, EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2);
|
|
1154
|
+
return envelope;
|
|
1155
|
+
}
|
|
1156
|
+
function openPaymentSlip(recipientIvsk, envelope) {
|
|
1157
|
+
if (envelope.length < EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2 + 16) return null;
|
|
1158
|
+
try {
|
|
1159
|
+
const ephPkPacked = envelope.subarray(0, EPH_PK_SIZE2);
|
|
1160
|
+
const ephPkPoint = (0, import_baby_jubjub4.unpackPoint)(bytesToBigintLE(ephPkPacked));
|
|
1161
|
+
if (!ephPkPoint) return null;
|
|
1162
|
+
const ivskScalar = bytesToBjjScalar(recipientIvsk);
|
|
1163
|
+
const sharedSecret = bigintTo32Le(fastMulPoint(ephPkPoint, ivskScalar)[0]);
|
|
1164
|
+
const slipKey = deriveSlipKey(sharedSecret);
|
|
1165
|
+
const suffix = envelope.subarray(EPH_PK_SIZE2, EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2);
|
|
1166
|
+
const sealed = envelope.subarray(EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2);
|
|
1167
|
+
const cipher = (0, import_chacha3.chacha20poly1305)(slipKey, buildNonce2(suffix));
|
|
1168
|
+
const plaintext = cipher.decrypt(sealed);
|
|
1169
|
+
const fields = JSON.parse(new TextDecoder().decode(plaintext));
|
|
1170
|
+
if (typeof fields.commitmentHex !== "string" || typeof fields.encryptedMemo !== "string") {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
return fields;
|
|
1174
|
+
} catch {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
var PAYMENT_SLIP_SCHEME = "orbslip1:";
|
|
1179
|
+
function slipChecksum(payload) {
|
|
1180
|
+
const digest = (0, import_sha24.sha256)(new TextEncoder().encode(PAYMENT_SLIP_SCHEME + payload));
|
|
1181
|
+
return toHex(digest.slice(0, 4)).slice(2);
|
|
1182
|
+
}
|
|
1183
|
+
function encodePaymentSlip(envelope) {
|
|
1184
|
+
const payload = base64UrlEncode(toHex(envelope));
|
|
1185
|
+
return `${PAYMENT_SLIP_SCHEME}${payload}:${slipChecksum(payload)}`;
|
|
1186
|
+
}
|
|
1187
|
+
function decodePaymentSlip(text) {
|
|
1188
|
+
if (!text.startsWith(PAYMENT_SLIP_SCHEME)) return null;
|
|
1189
|
+
const rest = text.slice(PAYMENT_SLIP_SCHEME.length);
|
|
1190
|
+
const sep = rest.lastIndexOf(":");
|
|
1191
|
+
if (sep < 0) return null;
|
|
1192
|
+
const payload = rest.slice(0, sep);
|
|
1193
|
+
const checksum = rest.slice(sep + 1);
|
|
1194
|
+
if (slipChecksum(payload) !== checksum) return null;
|
|
1195
|
+
try {
|
|
1196
|
+
const hex = base64UrlDecode(payload);
|
|
1197
|
+
const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1198
|
+
if (clean.length % 2 !== 0) return null;
|
|
1199
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
1200
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1201
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
1202
|
+
}
|
|
1203
|
+
return bytes;
|
|
1204
|
+
} catch {
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/protocol/eph/selfEph.ts
|
|
1210
|
+
var import_sha25 = require("@noble/hashes/sha2.js");
|
|
1211
|
+
var import_baby_jubjub5 = require("@zk-kit/baby-jubjub");
|
|
1043
1212
|
var SELF_EPH_DOMAIN = new TextEncoder().encode("orbinum-self-eph-v1");
|
|
1044
1213
|
function deriveSelfEphSk(spendingKey, index) {
|
|
1045
|
-
const h =
|
|
1214
|
+
const h = import_sha25.sha256.create();
|
|
1046
1215
|
h.update(SELF_EPH_DOMAIN);
|
|
1047
1216
|
h.update(bigintTo32Le(spendingKey));
|
|
1048
1217
|
const idx = new Uint8Array(4);
|
|
@@ -1051,7 +1220,7 @@ function deriveSelfEphSk(spendingKey, index) {
|
|
|
1051
1220
|
return h.digest();
|
|
1052
1221
|
}
|
|
1053
1222
|
function selfEphWindow(spendingKey, ivkPacked, from, count) {
|
|
1054
|
-
const ivkPoint = (0,
|
|
1223
|
+
const ivkPoint = (0, import_baby_jubjub5.unpackPoint)(bytesToBigintLE(ivkPacked));
|
|
1055
1224
|
if (!ivkPoint) throw new Error("selfEphWindow: invalid viewing public key");
|
|
1056
1225
|
const entries = [];
|
|
1057
1226
|
for (let i = from; i < from + count; i++) {
|
|
@@ -1060,7 +1229,7 @@ function selfEphWindow(spendingKey, ivkPacked, from, count) {
|
|
|
1060
1229
|
const sharedPoint = fastMulPoint(ivkPoint, scalar);
|
|
1061
1230
|
entries.push({
|
|
1062
1231
|
index: i,
|
|
1063
|
-
ephPkHex: toHex(bigintTo32Le((0,
|
|
1232
|
+
ephPkHex: toHex(bigintTo32Le((0, import_baby_jubjub5.packPoint)(ephPk))),
|
|
1064
1233
|
sharedSecret: bigintTo32Le(sharedPoint[0])
|
|
1065
1234
|
});
|
|
1066
1235
|
}
|
|
@@ -1068,17 +1237,17 @@ function selfEphWindow(spendingKey, ivkPacked, from, count) {
|
|
|
1068
1237
|
}
|
|
1069
1238
|
|
|
1070
1239
|
// src/protocol/eph/pairwiseEph.ts
|
|
1071
|
-
var
|
|
1072
|
-
var
|
|
1240
|
+
var import_sha26 = require("@noble/hashes/sha2.js");
|
|
1241
|
+
var import_baby_jubjub6 = require("@zk-kit/baby-jubjub");
|
|
1073
1242
|
var PAIRWISE_EPH_DOMAIN = new TextEncoder().encode("orbinum-pairwise-eph-v1");
|
|
1074
1243
|
function derivePairwiseSharedSecret(myViewingSk, theirIvkPacked) {
|
|
1075
|
-
const theirPoint = (0,
|
|
1244
|
+
const theirPoint = (0, import_baby_jubjub6.unpackPoint)(bytesToBigintLE(theirIvkPacked));
|
|
1076
1245
|
if (!theirPoint) throw new Error("derivePairwiseSharedSecret: invalid viewing public key");
|
|
1077
1246
|
const shared = fastMulPoint(theirPoint, bytesToBjjScalar(myViewingSk));
|
|
1078
1247
|
return bigintTo32Le(shared[0]);
|
|
1079
1248
|
}
|
|
1080
1249
|
function derivePairwiseEphSk(pairSecret, index) {
|
|
1081
|
-
const h =
|
|
1250
|
+
const h = import_sha26.sha256.create();
|
|
1082
1251
|
h.update(PAIRWISE_EPH_DOMAIN);
|
|
1083
1252
|
h.update(pairSecret);
|
|
1084
1253
|
const idx = new Uint8Array(4);
|
|
@@ -1087,7 +1256,7 @@ function derivePairwiseEphSk(pairSecret, index) {
|
|
|
1087
1256
|
return h.digest();
|
|
1088
1257
|
}
|
|
1089
1258
|
function pairwiseEphWindow(pairSecret, receiverIvkPacked, from, count) {
|
|
1090
|
-
const ivkPoint = (0,
|
|
1259
|
+
const ivkPoint = (0, import_baby_jubjub6.unpackPoint)(bytesToBigintLE(receiverIvkPacked));
|
|
1091
1260
|
if (!ivkPoint) throw new Error("pairwiseEphWindow: invalid viewing public key");
|
|
1092
1261
|
const entries = [];
|
|
1093
1262
|
for (let i = from; i < from + count; i++) {
|
|
@@ -1096,7 +1265,7 @@ function pairwiseEphWindow(pairSecret, receiverIvkPacked, from, count) {
|
|
|
1096
1265
|
const sharedPoint = fastMulPoint(ivkPoint, scalar);
|
|
1097
1266
|
entries.push({
|
|
1098
1267
|
index: i,
|
|
1099
|
-
ephPkHex: toHex(bigintTo32Le((0,
|
|
1268
|
+
ephPkHex: toHex(bigintTo32Le((0, import_baby_jubjub6.packPoint)(ephPk))),
|
|
1100
1269
|
sharedSecret: bigintTo32Le(sharedPoint[0])
|
|
1101
1270
|
});
|
|
1102
1271
|
}
|
|
@@ -1107,8 +1276,8 @@ function pairwiseEphWindow(pairSecret, receiverIvkPacked, from, count) {
|
|
|
1107
1276
|
var CURRENT_CIRCUIT_VERSION = 1;
|
|
1108
1277
|
|
|
1109
1278
|
// src/protocol/note/NoteBuilder.ts
|
|
1110
|
-
var
|
|
1111
|
-
var
|
|
1279
|
+
var import_baby_jubjub7 = require("@zk-kit/baby-jubjub");
|
|
1280
|
+
var import_utils4 = require("@noble/ciphers/utils.js");
|
|
1112
1281
|
var import_poseidon_lite = require("poseidon-lite");
|
|
1113
1282
|
var NoteBuilder = class {
|
|
1114
1283
|
/**
|
|
@@ -1137,13 +1306,13 @@ var NoteBuilder = class {
|
|
|
1137
1306
|
if (useStealth) {
|
|
1138
1307
|
const recipientOwnerPk = input.recipientOwnerPk;
|
|
1139
1308
|
const recipientIvkPacked = input.viewingPublicKey;
|
|
1140
|
-
const ephSk = (0,
|
|
1309
|
+
const ephSk = (0, import_utils4.randomBytes)(32);
|
|
1141
1310
|
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
1142
|
-
const ivkPoint = (0,
|
|
1311
|
+
const ivkPoint = (0, import_baby_jubjub7.unpackPoint)(ivkPackedBigint);
|
|
1143
1312
|
if (!ivkPoint)
|
|
1144
1313
|
throw new Error("NoteBuilder.build: invalid recipient viewing public key");
|
|
1145
1314
|
const ephSkScalar = BigInt(toHex(ephSk)) % BABYJUB_SUBORDER || 1n;
|
|
1146
|
-
const sharedPoint = (0,
|
|
1315
|
+
const sharedPoint = (0, import_baby_jubjub7.mulPointEscalar)(ivkPoint, ephSkScalar);
|
|
1147
1316
|
const sharedSecret = bigintTo32Le(sharedPoint[0]);
|
|
1148
1317
|
const recipientPkPoint = recoverOwnerPkPoint(recipientOwnerPk);
|
|
1149
1318
|
if (!recipientPkPoint)
|
|
@@ -1178,6 +1347,21 @@ var NoteBuilder = class {
|
|
|
1178
1347
|
throw new Error(
|
|
1179
1348
|
`NoteBuilder.build: invariant violated \u2014 memo must be ${ENCRYPTED_MEMO_SIZE} bytes, got ${memo.length}`
|
|
1180
1349
|
);
|
|
1350
|
+
let ovkBlob;
|
|
1351
|
+
if (input.outgoingViewingKey !== void 0) {
|
|
1352
|
+
const ephPkBytes = Uint8Array.from(memo.slice(148, 180));
|
|
1353
|
+
const sealed = sealOutgoingBlob(
|
|
1354
|
+
input.outgoingViewingKey,
|
|
1355
|
+
sharedSecret,
|
|
1356
|
+
stealthCommitmentBytes,
|
|
1357
|
+
ephPkBytes
|
|
1358
|
+
);
|
|
1359
|
+
if (sealed.length !== OVK_BLOB_SIZE)
|
|
1360
|
+
throw new Error(
|
|
1361
|
+
`NoteBuilder.build: invariant violated \u2014 ovkBlob must be ${OVK_BLOB_SIZE} bytes, got ${sealed.length}`
|
|
1362
|
+
);
|
|
1363
|
+
ovkBlob = Array.from(sealed);
|
|
1364
|
+
}
|
|
1181
1365
|
return {
|
|
1182
1366
|
value,
|
|
1183
1367
|
assetId,
|
|
@@ -1192,7 +1376,8 @@ var NoteBuilder = class {
|
|
|
1192
1376
|
commitmentHex: toHex(commitmentBytes2),
|
|
1193
1377
|
nullifierHex: toHex(nullifierBytes2),
|
|
1194
1378
|
memo,
|
|
1195
|
-
counterpartyPk
|
|
1379
|
+
counterpartyPk,
|
|
1380
|
+
...ovkBlob ? { ovkBlob } : {}
|
|
1196
1381
|
};
|
|
1197
1382
|
}
|
|
1198
1383
|
const commitment = (0, import_poseidon_lite.poseidon4)([value, assetId, ownerPk, blinding]);
|
|
@@ -1322,6 +1507,18 @@ function buildDummyTransferInput(assetId) {
|
|
|
1322
1507
|
}
|
|
1323
1508
|
|
|
1324
1509
|
// src/protocol/note/NoteDecryptor.ts
|
|
1510
|
+
function decryptAndVerifyPlaintext(memoBytes, commitmentBytes, sharedSecret, effectiveOwnerPk) {
|
|
1511
|
+
const plaintext = EncryptedMemo.decryptWithSharedSecret(
|
|
1512
|
+
memoBytes,
|
|
1513
|
+
commitmentBytes,
|
|
1514
|
+
sharedSecret
|
|
1515
|
+
);
|
|
1516
|
+
if (!plaintext) return null;
|
|
1517
|
+
const ownerPk = effectiveOwnerPk ?? plaintext.ownerPk;
|
|
1518
|
+
const recomputed = (0, import_poseidon_lite2.poseidon4)([plaintext.value, plaintext.assetId, ownerPk, plaintext.blinding]);
|
|
1519
|
+
if (recomputed !== bytesToBigintLE(commitmentBytes)) return null;
|
|
1520
|
+
return plaintext;
|
|
1521
|
+
}
|
|
1325
1522
|
function computeNullifier(commitment, spendingKey) {
|
|
1326
1523
|
return (0, import_poseidon_lite2.poseidon2)([commitment, spendingKey]);
|
|
1327
1524
|
}
|
|
@@ -1404,6 +1601,40 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
|
|
|
1404
1601
|
}
|
|
1405
1602
|
};
|
|
1406
1603
|
}
|
|
1604
|
+
function tryRecoverOutgoing(hint, ovk, opts) {
|
|
1605
|
+
if (!hint.ovkBlob || !hint.encryptedMemo) return null;
|
|
1606
|
+
let commitmentBytes;
|
|
1607
|
+
let memoBytes;
|
|
1608
|
+
let blobBytes;
|
|
1609
|
+
try {
|
|
1610
|
+
commitmentBytes = fromHex(hint.commitmentHex);
|
|
1611
|
+
memoBytes = fromHex(hint.encryptedMemo);
|
|
1612
|
+
blobBytes = fromHex(hint.ovkBlob);
|
|
1613
|
+
} catch {
|
|
1614
|
+
return null;
|
|
1615
|
+
}
|
|
1616
|
+
if (memoBytes.length !== ENCRYPTED_MEMO_SIZE) return null;
|
|
1617
|
+
if (blobBytes.length !== OVK_BLOB_SIZE) return null;
|
|
1618
|
+
const ephPkBytes = memoBytes.subarray(148, 180);
|
|
1619
|
+
const sharedSecret = openOutgoingBlob(ovk, blobBytes, commitmentBytes, ephPkBytes);
|
|
1620
|
+
if (!sharedSecret) return null;
|
|
1621
|
+
const activation = opts?.viewTagActivationLeaf;
|
|
1622
|
+
if (activation !== void 0 && isValidLeafIndex(hint.leafIndex) && hint.leafIndex >= activation && !EncryptedMemo.checkViewTag(memoBytes, sharedSecret)) {
|
|
1623
|
+
return null;
|
|
1624
|
+
}
|
|
1625
|
+
const plaintext = decryptAndVerifyPlaintext(memoBytes, commitmentBytes, sharedSecret, null);
|
|
1626
|
+
if (!plaintext) return null;
|
|
1627
|
+
return {
|
|
1628
|
+
commitmentHex: hint.commitmentHex,
|
|
1629
|
+
...isValidLeafIndex(hint.leafIndex) ? { leafIndex: hint.leafIndex } : {},
|
|
1630
|
+
value: plaintext.value,
|
|
1631
|
+
assetId: plaintext.assetId,
|
|
1632
|
+
recipientStealthPk: plaintext.ownerPk,
|
|
1633
|
+
blinding: plaintext.blinding,
|
|
1634
|
+
counterpartyPk: plaintext.counterpartyPk,
|
|
1635
|
+
circuitVersion: plaintext.circuitVersion
|
|
1636
|
+
};
|
|
1637
|
+
}
|
|
1407
1638
|
|
|
1408
1639
|
// src/protocol/note/NoteDisclosure.ts
|
|
1409
1640
|
var import_poseidon_lite3 = require("poseidon-lite");
|
|
@@ -1549,10 +1780,11 @@ ${canonicalAccountId(address)}`;
|
|
|
1549
1780
|
}
|
|
1550
1781
|
|
|
1551
1782
|
// src/protocol/keys/PrivacyKeys.ts
|
|
1552
|
-
var
|
|
1553
|
-
var
|
|
1554
|
-
var
|
|
1783
|
+
var import_hkdf4 = require("@noble/hashes/hkdf.js");
|
|
1784
|
+
var import_sha27 = require("@noble/hashes/sha2.js");
|
|
1785
|
+
var import_baby_jubjub8 = require("@zk-kit/baby-jubjub");
|
|
1555
1786
|
var IVK_DOMAIN = new TextEncoder().encode("orbinum-ivk-v1");
|
|
1787
|
+
var OVK_DOMAIN = new TextEncoder().encode("orbinum-ovk-v1");
|
|
1556
1788
|
var KEY_VERSION = "v2";
|
|
1557
1789
|
function deriveSpendingKeyFromMaster(masterBytes) {
|
|
1558
1790
|
const skBigint = BigInt(toHex(masterBytes)) % BABYJUB_SUBORDER;
|
|
@@ -1560,12 +1792,12 @@ function deriveSpendingKeyFromMaster(masterBytes) {
|
|
|
1560
1792
|
}
|
|
1561
1793
|
function deriveViewingSecretKey(spendingKey) {
|
|
1562
1794
|
const ikm = bigintTo32Le(spendingKey);
|
|
1563
|
-
return (0,
|
|
1795
|
+
return (0, import_hkdf4.hkdf)(import_sha27.sha256, ikm, void 0, IVK_DOMAIN, 32);
|
|
1564
1796
|
}
|
|
1565
1797
|
function deriveViewingPublicKey(ivsk) {
|
|
1566
1798
|
const ivskScalar = BigInt(toHex(ivsk)) % BABYJUB_SUBORDER || 1n;
|
|
1567
1799
|
const ivkPoint = fastMulBase(ivskScalar);
|
|
1568
|
-
const packed = (0,
|
|
1800
|
+
const packed = (0, import_baby_jubjub8.packPoint)(ivkPoint);
|
|
1569
1801
|
return bigintTo32Le(packed);
|
|
1570
1802
|
}
|
|
1571
1803
|
function deriveOwnerPk(spendingKey) {
|
|
@@ -1576,12 +1808,15 @@ function deriveOwnerPk(spendingKey) {
|
|
|
1576
1808
|
return 0n;
|
|
1577
1809
|
}
|
|
1578
1810
|
}
|
|
1811
|
+
function deriveOutgoingViewingKey(masterBytes) {
|
|
1812
|
+
return (0, import_hkdf4.hkdf)(import_sha27.sha256, masterBytes, void 0, OVK_DOMAIN, 32);
|
|
1813
|
+
}
|
|
1579
1814
|
|
|
1580
1815
|
// src/protocol/keys/PrivacyKeyManager.ts
|
|
1581
|
-
var
|
|
1816
|
+
var import_sha28 = require("@noble/hashes/sha2.js");
|
|
1582
1817
|
function privacyAddressChecksum(ownerPkHex, ivkHex) {
|
|
1583
1818
|
const body = `orbpriv2:${ownerPkHex}:${ivkHex}`;
|
|
1584
|
-
const digest = (0,
|
|
1819
|
+
const digest = (0, import_sha28.sha256)(new TextEncoder().encode(body));
|
|
1585
1820
|
return toHex(digest.slice(0, 4)).slice(2);
|
|
1586
1821
|
}
|
|
1587
1822
|
var PrivacyKeyManager = class {
|
|
@@ -1590,7 +1825,8 @@ var PrivacyKeyManager = class {
|
|
|
1590
1825
|
masterBytes: null,
|
|
1591
1826
|
viewingSecretKey: null,
|
|
1592
1827
|
viewingPublicKeyPacked: null,
|
|
1593
|
-
ownerPk: null
|
|
1828
|
+
ownerPk: null,
|
|
1829
|
+
outgoingViewingKey: null
|
|
1594
1830
|
};
|
|
1595
1831
|
/**
|
|
1596
1832
|
* Load a spending key and its corresponding master bytes into the in-memory session.
|
|
@@ -1605,12 +1841,14 @@ var PrivacyKeyManager = class {
|
|
|
1605
1841
|
const viewingSecretKey = deriveViewingSecretKey(spendingKey);
|
|
1606
1842
|
const viewingPublicKeyPacked = deriveViewingPublicKey(viewingSecretKey);
|
|
1607
1843
|
const ownerPk = deriveOwnerPk(spendingKey);
|
|
1844
|
+
const outgoingViewingKey = deriveOutgoingViewingKey(masterBytes);
|
|
1608
1845
|
this._state = {
|
|
1609
1846
|
spendingKey,
|
|
1610
1847
|
masterBytes,
|
|
1611
1848
|
viewingSecretKey,
|
|
1612
1849
|
viewingPublicKeyPacked,
|
|
1613
|
-
ownerPk
|
|
1850
|
+
ownerPk,
|
|
1851
|
+
outgoingViewingKey
|
|
1614
1852
|
};
|
|
1615
1853
|
}
|
|
1616
1854
|
/** Clear all key material from memory. Call on vault lock / sign-out. */
|
|
@@ -1620,7 +1858,8 @@ var PrivacyKeyManager = class {
|
|
|
1620
1858
|
masterBytes: null,
|
|
1621
1859
|
viewingSecretKey: null,
|
|
1622
1860
|
viewingPublicKeyPacked: null,
|
|
1623
|
-
ownerPk: null
|
|
1861
|
+
ownerPk: null,
|
|
1862
|
+
outgoingViewingKey: null
|
|
1624
1863
|
};
|
|
1625
1864
|
}
|
|
1626
1865
|
/** Returns true if a spending key has been loaded. */
|
|
@@ -1665,6 +1904,16 @@ var PrivacyKeyManager = class {
|
|
|
1665
1904
|
}
|
|
1666
1905
|
return this._state.ownerPk;
|
|
1667
1906
|
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Returns the 32-byte outgoing viewing key (ovk). Throws if not loaded.
|
|
1909
|
+
* Used to seal/open the outgoing blob that lets the sender recover a transfer.
|
|
1910
|
+
*/
|
|
1911
|
+
getOutgoingViewingKey() {
|
|
1912
|
+
if (this._state.outgoingViewingKey === null) {
|
|
1913
|
+
throw new Error("PrivacyKeyManager: no key loaded. Call load() first.");
|
|
1914
|
+
}
|
|
1915
|
+
return this._state.outgoingViewingKey;
|
|
1916
|
+
}
|
|
1668
1917
|
/** Returns the spending key as a 32-byte little-endian Uint8Array. Throws if not loaded. */
|
|
1669
1918
|
getSpendingKeyBytes() {
|
|
1670
1919
|
return bigintTo32Le(this.getSpendingKey());
|
|
@@ -1766,8 +2015,8 @@ var PrivacyKeyManager = class {
|
|
|
1766
2015
|
};
|
|
1767
2016
|
|
|
1768
2017
|
// src/protocol/keys/spendingKeyDerivation.ts
|
|
1769
|
-
var
|
|
1770
|
-
var
|
|
2018
|
+
var import_hkdf5 = require("@noble/hashes/hkdf.js");
|
|
2019
|
+
var import_sha29 = require("@noble/hashes/sha2.js");
|
|
1771
2020
|
var MIN_SIGNATURE_BYTES = 32;
|
|
1772
2021
|
var MIN_DISTINCT_BYTES = 8;
|
|
1773
2022
|
function assertUsableSignature(sigBytes) {
|
|
@@ -1789,7 +2038,7 @@ async function deriveMasterKeyBytes(signatureHex, chainId, address) {
|
|
|
1789
2038
|
const info = new TextEncoder().encode(
|
|
1790
2039
|
`orbinum-sk-${KEY_VERSION}:${chainId}:${canonicalAccountId(address)}`
|
|
1791
2040
|
);
|
|
1792
|
-
return (0,
|
|
2041
|
+
return (0, import_hkdf5.hkdf)(import_sha29.sha256, sigBytes, new Uint8Array(0), info, 32);
|
|
1793
2042
|
}
|
|
1794
2043
|
async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
|
|
1795
2044
|
return deriveSpendingKeyFromMaster(await deriveMasterKeyBytes(signatureHex, chainId, address));
|
|
@@ -1797,8 +2046,8 @@ async function deriveSpendingKeyFromSignature(signatureHex, chainId, address) {
|
|
|
1797
2046
|
|
|
1798
2047
|
// src/protocol/proving/unshield.ts
|
|
1799
2048
|
var import_proof_generator = require("@orbinum/proof-generator");
|
|
1800
|
-
var
|
|
1801
|
-
var
|
|
2049
|
+
var import_utils5 = require("@noble/ciphers/utils.js");
|
|
2050
|
+
var import_baby_jubjub9 = require("@zk-kit/baby-jubjub");
|
|
1802
2051
|
var import_poseidon_lite4 = require("poseidon-lite");
|
|
1803
2052
|
|
|
1804
2053
|
// src/protocol/proving/merkle.ts
|
|
@@ -1830,8 +2079,8 @@ async function generateUnshieldProof(inputs, options = {}) {
|
|
|
1830
2079
|
if (changeValue < 0n) {
|
|
1831
2080
|
throw new Error("changeValue must be >= 0.");
|
|
1832
2081
|
}
|
|
1833
|
-
const changeOwnerPubkey = inputs.changeOwnerPubkey ?? (0,
|
|
1834
|
-
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE((0,
|
|
2082
|
+
const changeOwnerPubkey = inputs.changeOwnerPubkey ?? (0, import_baby_jubjub9.mulPointEscalar)(import_baby_jubjub9.Base8, inputs.spendingKey)[0];
|
|
2083
|
+
const changeBlinding = inputs.changeBlinding ?? (changeValue > 0n ? bytesToBigintLE((0, import_utils5.randomBytes)(32)) : 0n);
|
|
1835
2084
|
const changeCommitment = changeValue > 0n ? (0, import_poseidon_lite4.poseidon4)([changeValue, inputs.assetId, changeOwnerPubkey, changeBlinding]) : 0n;
|
|
1836
2085
|
const circuitInputs = {
|
|
1837
2086
|
merkle_root: leHexToBigint(inputs.merkleRoot).toString(),
|
|
@@ -5675,6 +5924,7 @@ async function processHints(ctx, hints, total) {
|
|
|
5675
5924
|
pageEntries.push({ note, isNew });
|
|
5676
5925
|
}
|
|
5677
5926
|
if (pageEntries.length > 0) await ctx.onPage?.(pageEntries);
|
|
5927
|
+
await ctx.onBatchDone?.(outcome.maxLeafIndex);
|
|
5678
5928
|
ctx.onProgress?.({ scanned: outcome.scanned, total, found: outcome.found });
|
|
5679
5929
|
}
|
|
5680
5930
|
async function processSealedChunks(ctx, source, sinceLeafIndex) {
|
|
@@ -5709,6 +5959,7 @@ async function processSealedChunks(ctx, source, sinceLeafIndex) {
|
|
|
5709
5959
|
}
|
|
5710
5960
|
async function collectScanEntries(params) {
|
|
5711
5961
|
const { source, pool, keys, existingHexes, sinceLeafIndex, onProgress, signal, onPage } = params;
|
|
5962
|
+
const { onBatchDone } = params;
|
|
5712
5963
|
if (signal?.aborted) throw scanAbortError();
|
|
5713
5964
|
const outcome = {
|
|
5714
5965
|
scanEntries: [],
|
|
@@ -5732,7 +5983,8 @@ async function collectScanEntries(params) {
|
|
|
5732
5983
|
vaultHexes: params.vaultHexes ?? existingHexes,
|
|
5733
5984
|
onProgress,
|
|
5734
5985
|
signal,
|
|
5735
|
-
onPage
|
|
5986
|
+
onPage,
|
|
5987
|
+
onBatchDone
|
|
5736
5988
|
};
|
|
5737
5989
|
let tailSince = sinceLeafIndex;
|
|
5738
5990
|
try {
|
|
@@ -5773,63 +6025,12 @@ async function collectScanEntries(params) {
|
|
|
5773
6025
|
return outcome;
|
|
5774
6026
|
}
|
|
5775
6027
|
|
|
5776
|
-
// src/wallet/worker/kernel/types.ts
|
|
5777
|
-
var SELF_EPH_WINDOW = 1024;
|
|
5778
|
-
var PAIRWISE_EPH_WINDOW = 64;
|
|
5779
|
-
var EMPTY_BATCH_RESULT = {
|
|
5780
|
-
notes: [],
|
|
5781
|
-
tagFiltered: 0,
|
|
5782
|
-
selfMatched: 0,
|
|
5783
|
-
pairwiseMatched: 0,
|
|
5784
|
-
maxSelfEphIndex: null
|
|
5785
|
-
};
|
|
5786
|
-
|
|
5787
|
-
// src/wallet/scanner/selfEphGap.ts
|
|
5788
|
-
function noteEphPkHex(note) {
|
|
5789
|
-
if (!Array.isArray(note.memo) || note.memo.length < 32) return null;
|
|
5790
|
-
return toHex(note.memo.slice(-32)).toLowerCase();
|
|
5791
|
-
}
|
|
5792
|
-
function gapMargin(windowSize) {
|
|
5793
|
-
return Math.max(1, Math.floor(windowSize / 16));
|
|
5794
|
-
}
|
|
5795
|
-
function resolveSelfEphCeiling(params) {
|
|
5796
|
-
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
5797
|
-
const windowSize = params.windowSize ?? SELF_EPH_WINDOW;
|
|
5798
|
-
if (scanMaxIndex === null) return null;
|
|
5799
|
-
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
5800
|
-
const ownEphPks = /* @__PURE__ */ new Set();
|
|
5801
|
-
for (const note of notes) {
|
|
5802
|
-
const hex = noteEphPkHex(note);
|
|
5803
|
-
if (hex) ownEphPks.add(hex);
|
|
5804
|
-
}
|
|
5805
|
-
const ivkPacked = deriveViewingPublicKey(viewingKey);
|
|
5806
|
-
let max = scanMaxIndex;
|
|
5807
|
-
let from = windowSize;
|
|
5808
|
-
for (; ; ) {
|
|
5809
|
-
const slice = selfEphWindow(spendingKey, ivkPacked, from, windowSize);
|
|
5810
|
-
let matched = false;
|
|
5811
|
-
for (const entry of slice) {
|
|
5812
|
-
if (ownEphPks.has(entry.ephPkHex.toLowerCase())) {
|
|
5813
|
-
matched = true;
|
|
5814
|
-
if (entry.index > max) max = entry.index;
|
|
5815
|
-
}
|
|
5816
|
-
}
|
|
5817
|
-
if (!matched) break;
|
|
5818
|
-
from += windowSize;
|
|
5819
|
-
}
|
|
5820
|
-
return max;
|
|
5821
|
-
}
|
|
5822
|
-
function windowSizeForCounter(counter) {
|
|
5823
|
-
const needed = counter + gapMargin(SELF_EPH_WINDOW);
|
|
5824
|
-
return Math.max(SELF_EPH_WINDOW, Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW);
|
|
5825
|
-
}
|
|
5826
|
-
|
|
5827
6028
|
// src/wallet/scanner/phases/spentSet.ts
|
|
5828
6029
|
function normalizeNullifierHex(hex) {
|
|
5829
6030
|
return hex.toLowerCase();
|
|
5830
6031
|
}
|
|
5831
|
-
async function
|
|
5832
|
-
const { source, cache,
|
|
6032
|
+
async function openSpentSet(params) {
|
|
6033
|
+
const { source, cache, signal } = params;
|
|
5833
6034
|
const throwIfAborted = () => {
|
|
5834
6035
|
if (signal?.aborted) throw scanAbortError();
|
|
5835
6036
|
};
|
|
@@ -5838,20 +6039,29 @@ async function resolveSpentSet(params) {
|
|
|
5838
6039
|
await syncNullifierCache(params, manifest, throwIfAborted);
|
|
5839
6040
|
throwIfAborted();
|
|
5840
6041
|
const tail = await getConsistentTail(params, throwIfAborted);
|
|
5841
|
-
const stored = await cache.getSpentNullifiers([...ownNullifiers]);
|
|
5842
6042
|
const tailMap = new Map(
|
|
5843
6043
|
tail.data.map((h, i) => [
|
|
5844
6044
|
normalizeNullifierHex(h),
|
|
5845
6045
|
{ spentAt: tail.timestampsMs?.[i] ?? null, txHash: tail.txHashes?.[i] ?? null }
|
|
5846
6046
|
])
|
|
5847
6047
|
);
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
6048
|
+
return {
|
|
6049
|
+
async query(ownNullifiers) {
|
|
6050
|
+
if (ownNullifiers.size === 0) return /* @__PURE__ */ new Map();
|
|
6051
|
+
const stored = await cache.getSpentNullifiers([...ownNullifiers]);
|
|
6052
|
+
const spent = /* @__PURE__ */ new Map();
|
|
6053
|
+
const unknown = { spentAt: null, txHash: null };
|
|
6054
|
+
for (const h of ownNullifiers) {
|
|
6055
|
+
if (stored.has(h)) spent.set(h, stored.get(h) ?? unknown);
|
|
6056
|
+
else if (tailMap.has(h)) spent.set(h, tailMap.get(h) ?? unknown);
|
|
6057
|
+
}
|
|
6058
|
+
return spent;
|
|
6059
|
+
}
|
|
6060
|
+
};
|
|
6061
|
+
}
|
|
6062
|
+
async function resolveSpentSet(params) {
|
|
6063
|
+
const handle = await openSpentSet(params);
|
|
6064
|
+
return handle.query(params.ownNullifiers);
|
|
5855
6065
|
}
|
|
5856
6066
|
async function syncNullifierCache(params, manifest, throwIfAborted) {
|
|
5857
6067
|
const { source, cache } = params;
|
|
@@ -5901,40 +6111,6 @@ async function getConsistentTail(params, throwIfAborted) {
|
|
|
5901
6111
|
throw new Error("nullifier tail inconsistent with local chunks after retry");
|
|
5902
6112
|
}
|
|
5903
6113
|
|
|
5904
|
-
// src/wallet/scanner/phases/spentStatus.ts
|
|
5905
|
-
function collectNullifiersToQuery(scanEntries, onChainHexes, vaultNotes) {
|
|
5906
|
-
const syncedCommitments = new Set(scanEntries.map((e) => e.note.commitmentHex));
|
|
5907
|
-
const nullifiers = /* @__PURE__ */ new Set();
|
|
5908
|
-
for (const { note } of scanEntries) {
|
|
5909
|
-
nullifiers.add(note.nullifierHex.toLowerCase());
|
|
5910
|
-
}
|
|
5911
|
-
for (const vaultNote of vaultNotes) {
|
|
5912
|
-
if (syncedCommitments.has(vaultNote.commitmentHex)) continue;
|
|
5913
|
-
if (!onChainHexes.has(vaultNote.commitmentHex)) continue;
|
|
5914
|
-
if (vaultNote.nullifierHex) nullifiers.add(vaultNote.nullifierHex.toLowerCase());
|
|
5915
|
-
}
|
|
5916
|
-
return nullifiers;
|
|
5917
|
-
}
|
|
5918
|
-
async function resolveSpentStatus(params) {
|
|
5919
|
-
if (params.nullifiers.size === 0) return /* @__PURE__ */ new Map();
|
|
5920
|
-
try {
|
|
5921
|
-
return await resolveSpentSet({
|
|
5922
|
-
source: params.source,
|
|
5923
|
-
cache: params.cache,
|
|
5924
|
-
ownNullifiers: params.nullifiers,
|
|
5925
|
-
signal: params.signal,
|
|
5926
|
-
onWarning: params.onWarning
|
|
5927
|
-
});
|
|
5928
|
-
} catch (err) {
|
|
5929
|
-
if (isAbortError(err)) throw err;
|
|
5930
|
-
params.onWarning?.(
|
|
5931
|
-
"could not resolve spent-nullifier status (feed unavailable or inconsistent); spent status left unverified this pass",
|
|
5932
|
-
err
|
|
5933
|
-
);
|
|
5934
|
-
return /* @__PURE__ */ new Map();
|
|
5935
|
-
}
|
|
5936
|
-
}
|
|
5937
|
-
|
|
5938
6114
|
// src/wallet/scanner/phases/persist.ts
|
|
5939
6115
|
function purgeIsTrustworthy(hintsScanned, purgeableNotes) {
|
|
5940
6116
|
if (purgeableNotes === 0) return true;
|
|
@@ -5993,6 +6169,134 @@ async function persistCursor(storage, maxLeafIndex, isIncremental) {
|
|
|
5993
6169
|
});
|
|
5994
6170
|
}
|
|
5995
6171
|
|
|
6172
|
+
// src/wallet/scanner/phases/checkpoint.ts
|
|
6173
|
+
function createScanCheckpoint(params) {
|
|
6174
|
+
const { vault, storage, nullifiers, isIncremental, signal, onWarning } = params;
|
|
6175
|
+
const throwIfAborted = () => {
|
|
6176
|
+
if (signal?.aborted) throw scanAbortError();
|
|
6177
|
+
};
|
|
6178
|
+
let spentSetPromise = null;
|
|
6179
|
+
const getSpentSet = () => spentSetPromise ??= openSpentSet({
|
|
6180
|
+
source: nullifiers,
|
|
6181
|
+
cache: storage,
|
|
6182
|
+
signal,
|
|
6183
|
+
onWarning
|
|
6184
|
+
}).catch(() => null);
|
|
6185
|
+
return {
|
|
6186
|
+
async savePageNotes(entries) {
|
|
6187
|
+
throwIfAborted();
|
|
6188
|
+
const fresh = entries.filter((e) => e.isNew);
|
|
6189
|
+
if (fresh.length === 0) return;
|
|
6190
|
+
const own = new Set(fresh.map((e) => e.note.nullifierHex.toLowerCase()));
|
|
6191
|
+
const spentSet = await getSpentSet();
|
|
6192
|
+
const spent = spentSet ? await spentSet.query(own).catch(() => /* @__PURE__ */ new Map()) : /* @__PURE__ */ new Map();
|
|
6193
|
+
throwIfAborted();
|
|
6194
|
+
await vault.saveMany(
|
|
6195
|
+
fresh.map(({ note }) => {
|
|
6196
|
+
const spend = spent.get(note.nullifierHex.toLowerCase());
|
|
6197
|
+
return {
|
|
6198
|
+
note: spend ? stampSpentTxHash(note, spend.txHash) : note,
|
|
6199
|
+
noteStatus: {
|
|
6200
|
+
spent: spend !== void 0,
|
|
6201
|
+
spentAt: spend?.spentAt ?? null
|
|
6202
|
+
}
|
|
6203
|
+
};
|
|
6204
|
+
})
|
|
6205
|
+
);
|
|
6206
|
+
},
|
|
6207
|
+
async advanceCursor(maxLeafIndex) {
|
|
6208
|
+
if (maxLeafIndex === void 0) return;
|
|
6209
|
+
throwIfAborted();
|
|
6210
|
+
await persistCursor(storage, maxLeafIndex, isIncremental);
|
|
6211
|
+
}
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
// src/wallet/worker/kernel/types.ts
|
|
6216
|
+
var SELF_EPH_WINDOW = 1024;
|
|
6217
|
+
var PAIRWISE_EPH_WINDOW = 64;
|
|
6218
|
+
var EMPTY_BATCH_RESULT = {
|
|
6219
|
+
notes: [],
|
|
6220
|
+
tagFiltered: 0,
|
|
6221
|
+
selfMatched: 0,
|
|
6222
|
+
pairwiseMatched: 0,
|
|
6223
|
+
maxSelfEphIndex: null
|
|
6224
|
+
};
|
|
6225
|
+
|
|
6226
|
+
// src/wallet/scanner/selfEphGap.ts
|
|
6227
|
+
function noteEphPkHex(note) {
|
|
6228
|
+
if (!Array.isArray(note.memo) || note.memo.length < 32) return null;
|
|
6229
|
+
return toHex(note.memo.slice(-32)).toLowerCase();
|
|
6230
|
+
}
|
|
6231
|
+
function gapMargin(windowSize) {
|
|
6232
|
+
return Math.max(1, Math.floor(windowSize / 16));
|
|
6233
|
+
}
|
|
6234
|
+
function resolveSelfEphCeiling(params) {
|
|
6235
|
+
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
6236
|
+
const windowSize = params.windowSize ?? SELF_EPH_WINDOW;
|
|
6237
|
+
if (scanMaxIndex === null) return null;
|
|
6238
|
+
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
6239
|
+
const ownEphPks = /* @__PURE__ */ new Set();
|
|
6240
|
+
for (const note of notes) {
|
|
6241
|
+
const hex = noteEphPkHex(note);
|
|
6242
|
+
if (hex) ownEphPks.add(hex);
|
|
6243
|
+
}
|
|
6244
|
+
const ivkPacked = deriveViewingPublicKey(viewingKey);
|
|
6245
|
+
let max = scanMaxIndex;
|
|
6246
|
+
let from = windowSize;
|
|
6247
|
+
for (; ; ) {
|
|
6248
|
+
const slice = selfEphWindow(spendingKey, ivkPacked, from, windowSize);
|
|
6249
|
+
let matched = false;
|
|
6250
|
+
for (const entry of slice) {
|
|
6251
|
+
if (ownEphPks.has(entry.ephPkHex.toLowerCase())) {
|
|
6252
|
+
matched = true;
|
|
6253
|
+
if (entry.index > max) max = entry.index;
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
if (!matched) break;
|
|
6257
|
+
from += windowSize;
|
|
6258
|
+
}
|
|
6259
|
+
return max;
|
|
6260
|
+
}
|
|
6261
|
+
function windowSizeForCounter(counter) {
|
|
6262
|
+
const needed = counter + gapMargin(SELF_EPH_WINDOW);
|
|
6263
|
+
return Math.max(SELF_EPH_WINDOW, Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW);
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6266
|
+
// src/wallet/scanner/phases/spentStatus.ts
|
|
6267
|
+
function collectNullifiersToQuery(scanEntries, onChainHexes, vaultNotes) {
|
|
6268
|
+
const syncedCommitments = new Set(scanEntries.map((e) => e.note.commitmentHex));
|
|
6269
|
+
const nullifiers = /* @__PURE__ */ new Set();
|
|
6270
|
+
for (const { note } of scanEntries) {
|
|
6271
|
+
nullifiers.add(note.nullifierHex.toLowerCase());
|
|
6272
|
+
}
|
|
6273
|
+
for (const vaultNote of vaultNotes) {
|
|
6274
|
+
if (syncedCommitments.has(vaultNote.commitmentHex)) continue;
|
|
6275
|
+
if (!onChainHexes.has(vaultNote.commitmentHex)) continue;
|
|
6276
|
+
if (vaultNote.nullifierHex) nullifiers.add(vaultNote.nullifierHex.toLowerCase());
|
|
6277
|
+
}
|
|
6278
|
+
return nullifiers;
|
|
6279
|
+
}
|
|
6280
|
+
async function resolveSpentStatus(params) {
|
|
6281
|
+
if (params.nullifiers.size === 0) return /* @__PURE__ */ new Map();
|
|
6282
|
+
try {
|
|
6283
|
+
return await resolveSpentSet({
|
|
6284
|
+
source: params.source,
|
|
6285
|
+
cache: params.cache,
|
|
6286
|
+
ownNullifiers: params.nullifiers,
|
|
6287
|
+
signal: params.signal,
|
|
6288
|
+
onWarning: params.onWarning
|
|
6289
|
+
});
|
|
6290
|
+
} catch (err) {
|
|
6291
|
+
if (isAbortError(err)) throw err;
|
|
6292
|
+
params.onWarning?.(
|
|
6293
|
+
"could not resolve spent-nullifier status (feed unavailable or inconsistent); spent status left unverified this pass",
|
|
6294
|
+
err
|
|
6295
|
+
);
|
|
6296
|
+
return /* @__PURE__ */ new Map();
|
|
6297
|
+
}
|
|
6298
|
+
}
|
|
6299
|
+
|
|
5996
6300
|
// src/wallet/scanner/pipeline.ts
|
|
5997
6301
|
async function runScan(params) {
|
|
5998
6302
|
const { vault, storage, hints, nullifiers, pool, keys, sinceLeafIndex, signal, onWarning } = params;
|
|
@@ -6011,6 +6315,14 @@ async function runScan(params) {
|
|
|
6011
6315
|
}
|
|
6012
6316
|
}
|
|
6013
6317
|
);
|
|
6318
|
+
const checkpoint = createScanCheckpoint({
|
|
6319
|
+
vault,
|
|
6320
|
+
storage,
|
|
6321
|
+
nullifiers,
|
|
6322
|
+
isIncremental,
|
|
6323
|
+
signal,
|
|
6324
|
+
onWarning
|
|
6325
|
+
});
|
|
6014
6326
|
const scan = await collectScanEntries({
|
|
6015
6327
|
source: hints,
|
|
6016
6328
|
pool,
|
|
@@ -6031,7 +6343,9 @@ async function runScan(params) {
|
|
|
6031
6343
|
// consumer probes it with a vault commitment, so keeping the whole pool
|
|
6032
6344
|
// only bought heap.
|
|
6033
6345
|
vaultHexes: preScanHexes,
|
|
6034
|
-
onWarning
|
|
6346
|
+
onWarning,
|
|
6347
|
+
onPage: (entries) => checkpoint.savePageNotes(entries),
|
|
6348
|
+
onBatchDone: (maxLeafIndex) => checkpoint.advanceCursor(maxLeafIndex)
|
|
6035
6349
|
});
|
|
6036
6350
|
const nullifiersToQuery = collectNullifiersToQuery(
|
|
6037
6351
|
scan.scanEntries,
|
|
@@ -6404,6 +6718,22 @@ function isEntry2(value) {
|
|
|
6404
6718
|
return true;
|
|
6405
6719
|
}
|
|
6406
6720
|
|
|
6721
|
+
// src/wallet/ops/notes/paymentSlipImport.ts
|
|
6722
|
+
function importPaymentSlip(slip, keys) {
|
|
6723
|
+
const envelope = typeof slip === "string" ? decodePaymentSlip(slip) : slip;
|
|
6724
|
+
if (!envelope) return null;
|
|
6725
|
+
const fields = openPaymentSlip(keys.viewingSecretKey, envelope);
|
|
6726
|
+
if (!fields) return null;
|
|
6727
|
+
const commitment = {
|
|
6728
|
+
commitmentHex: fields.commitmentHex,
|
|
6729
|
+
leafIndex: fields.leafIndex ?? -1,
|
|
6730
|
+
encryptedMemo: fields.encryptedMemo
|
|
6731
|
+
};
|
|
6732
|
+
const note = tryDecryptNote(commitment, keys.viewingSecretKey, keys.spendingKey, keys.ownerPk);
|
|
6733
|
+
if (!note) return null;
|
|
6734
|
+
return fields.txHash ? stampCreatedTxHash(note, fields.txHash) : note;
|
|
6735
|
+
}
|
|
6736
|
+
|
|
6407
6737
|
// src/wallet/ops/spend/transfer.ts
|
|
6408
6738
|
var import_proof_generator6 = require("@orbinum/proof-generator");
|
|
6409
6739
|
|
|
@@ -6613,6 +6943,18 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
6613
6943
|
await deps.vault.save(stampCreatedTxHash(selfNote, txResult.txHash, txKind));
|
|
6614
6944
|
}
|
|
6615
6945
|
}
|
|
6946
|
+
const isSelf = deps.selfOwnerPk !== null && recipientPk === deps.selfOwnerPk;
|
|
6947
|
+
if (recipientViewingPublicKey !== void 0 && !isSelf) {
|
|
6948
|
+
try {
|
|
6949
|
+
const envelope = sealPaymentSlip(recipientViewingPublicKey, {
|
|
6950
|
+
commitmentHex: recipientNote.commitmentHex,
|
|
6951
|
+
encryptedMemo: toHex(Uint8Array.from(recipientNote.memo)),
|
|
6952
|
+
...txResult.txHash ? { txHash: txResult.txHash } : {}
|
|
6953
|
+
});
|
|
6954
|
+
return { ...txResult, paymentSlip: encodePaymentSlip(envelope) };
|
|
6955
|
+
} catch {
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6616
6958
|
}
|
|
6617
6959
|
return txResult;
|
|
6618
6960
|
}
|
|
@@ -7496,11 +7838,13 @@ var OrbinumWallet = class {
|
|
|
7496
7838
|
NOTE_BIGINT_FIELDS,
|
|
7497
7839
|
NOTE_TRANSFER_URI_SCHEME,
|
|
7498
7840
|
NoteBuilder,
|
|
7841
|
+
OVK_BLOB_SIZE,
|
|
7499
7842
|
OrbinumClient,
|
|
7500
7843
|
OrbinumClientProvider,
|
|
7501
7844
|
OrbinumWallet,
|
|
7502
7845
|
PAGE_SIZE,
|
|
7503
7846
|
PAIRWISE_EPH_WINDOW,
|
|
7847
|
+
PAYMENT_SLIP_SCHEME,
|
|
7504
7848
|
PRECOMPILE_ADDR,
|
|
7505
7849
|
PrivacyKeyManager,
|
|
7506
7850
|
PrivacyModule,
|
|
@@ -7571,11 +7915,14 @@ var OrbinumWallet = class {
|
|
|
7571
7915
|
decodeNoteBackup,
|
|
7572
7916
|
decodeNoteDisclosureKey,
|
|
7573
7917
|
decodeNoteTransferPage,
|
|
7918
|
+
decodePaymentSlip,
|
|
7574
7919
|
decodePrecompileCalldata,
|
|
7575
7920
|
decryptHintBatch,
|
|
7576
7921
|
decryptJson,
|
|
7577
7922
|
decryptNoteRecord,
|
|
7578
7923
|
deriveMasterKeyBytes,
|
|
7924
|
+
deriveOutgoingCipherKey,
|
|
7925
|
+
deriveOutgoingViewingKey,
|
|
7579
7926
|
deriveOwnerPk,
|
|
7580
7927
|
derivePairwiseEphSk,
|
|
7581
7928
|
derivePairwiseSharedSecret,
|
|
@@ -7594,6 +7941,7 @@ var OrbinumWallet = class {
|
|
|
7594
7941
|
detectCommitmentMismatch,
|
|
7595
7942
|
encodeNoteBackup,
|
|
7596
7943
|
encodeNoteTransferPages,
|
|
7944
|
+
encodePaymentSlip,
|
|
7597
7945
|
encryptJson,
|
|
7598
7946
|
encryptNote,
|
|
7599
7947
|
ensureCreatedAt,
|
|
@@ -7630,6 +7978,7 @@ var OrbinumWallet = class {
|
|
|
7630
7978
|
implicitSubstrateToEvm,
|
|
7631
7979
|
importDeviceKey,
|
|
7632
7980
|
importNotesFromBackup,
|
|
7981
|
+
importPaymentSlip,
|
|
7633
7982
|
isAbortError,
|
|
7634
7983
|
isAlreadySpentError,
|
|
7635
7984
|
isConnectionLossError,
|
|
@@ -7659,6 +8008,8 @@ var OrbinumWallet = class {
|
|
|
7659
8008
|
noteSpentTxHash,
|
|
7660
8009
|
noteToTransferEntry,
|
|
7661
8010
|
noteTxKind,
|
|
8011
|
+
openOutgoingBlob,
|
|
8012
|
+
openPaymentSlip,
|
|
7662
8013
|
pairwiseEphWindow,
|
|
7663
8014
|
palletErrorKind,
|
|
7664
8015
|
parseAmount,
|
|
@@ -7668,6 +8019,7 @@ var OrbinumWallet = class {
|
|
|
7668
8019
|
planTransfer,
|
|
7669
8020
|
planUnshield,
|
|
7670
8021
|
randomBlinding,
|
|
8022
|
+
randomOutgoingBlob,
|
|
7671
8023
|
reconstructOutgoingTxRecords,
|
|
7672
8024
|
recoverOwnerPkPoint,
|
|
7673
8025
|
recoverSelfStealthNote,
|
|
@@ -7683,6 +8035,8 @@ var OrbinumWallet = class {
|
|
|
7683
8035
|
runScan,
|
|
7684
8036
|
scalarToHex,
|
|
7685
8037
|
scanAbortError,
|
|
8038
|
+
sealOutgoingBlob,
|
|
8039
|
+
sealPaymentSlip,
|
|
7686
8040
|
selectGhosts,
|
|
7687
8041
|
selectNotes,
|
|
7688
8042
|
selfEphWindow,
|
|
@@ -7706,6 +8060,7 @@ var OrbinumWallet = class {
|
|
|
7706
8060
|
truncateMiddle,
|
|
7707
8061
|
tryDecryptNote,
|
|
7708
8062
|
tryDecryptNoteVerbose,
|
|
8063
|
+
tryRecoverOutgoing,
|
|
7709
8064
|
txLandedAfterError,
|
|
7710
8065
|
u128,
|
|
7711
8066
|
u64,
|