@notrace/stealth-sdk 1.0.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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/index.cjs +340 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +325 -0
- package/dist/index.d.ts +325 -0
- package/dist/index.js +317 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
- package/src/base58.ts +68 -0
- package/src/bytes.ts +48 -0
- package/src/index.ts +53 -0
- package/src/keys.ts +62 -0
- package/src/memo.ts +47 -0
- package/src/payLink.ts +50 -0
- package/src/scan.ts +212 -0
- package/src/sign.ts +64 -0
- package/src/stealth.ts +125 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
2
|
+
import { sha512 } from '@noble/hashes/sha2';
|
|
3
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
4
|
+
|
|
5
|
+
// src/bytes.ts
|
|
6
|
+
function concatBytes(...arrs) {
|
|
7
|
+
let total = 0;
|
|
8
|
+
for (const a of arrs) total += a.length;
|
|
9
|
+
const out = new Uint8Array(total);
|
|
10
|
+
let off = 0;
|
|
11
|
+
for (const a of arrs) {
|
|
12
|
+
out.set(a, off);
|
|
13
|
+
off += a.length;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function bytesToNumberLE(bytes) {
|
|
18
|
+
let n = 0n;
|
|
19
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
20
|
+
n = n << 8n | BigInt(bytes[i]);
|
|
21
|
+
}
|
|
22
|
+
return n;
|
|
23
|
+
}
|
|
24
|
+
function numberToBytesLE(n, len) {
|
|
25
|
+
const out = new Uint8Array(len);
|
|
26
|
+
let x = BigInt(n);
|
|
27
|
+
for (let i = 0; i < len; i++) {
|
|
28
|
+
out[i] = Number(x & 0xffn);
|
|
29
|
+
x >>= 8n;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
function bytesEqual(a, b) {
|
|
34
|
+
if (a.length !== b.length) return false;
|
|
35
|
+
for (let i = 0; i < a.length; i++) {
|
|
36
|
+
if (a[i] !== b[i]) return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/base58.ts
|
|
42
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
43
|
+
var ALPHABET_MAP = (() => {
|
|
44
|
+
const m = new Int8Array(128).fill(-1);
|
|
45
|
+
for (let i = 0; i < ALPHABET.length; i++) m[ALPHABET.charCodeAt(i)] = i;
|
|
46
|
+
return m;
|
|
47
|
+
})();
|
|
48
|
+
function bs58encode(bytes) {
|
|
49
|
+
if (bytes.length === 0) return "";
|
|
50
|
+
let zeros = 0;
|
|
51
|
+
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
52
|
+
const size = (bytes.length - zeros) * 138 / 100 + 1 | 0;
|
|
53
|
+
const b58 = new Uint8Array(size);
|
|
54
|
+
let length = 0;
|
|
55
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
56
|
+
let carry = bytes[i];
|
|
57
|
+
let j = 0;
|
|
58
|
+
for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
59
|
+
carry += 256 * b58[k];
|
|
60
|
+
b58[k] = carry % 58;
|
|
61
|
+
carry = carry / 58 | 0;
|
|
62
|
+
}
|
|
63
|
+
length = j;
|
|
64
|
+
}
|
|
65
|
+
let it = size - length;
|
|
66
|
+
while (it < size && b58[it] === 0) it++;
|
|
67
|
+
let out = "";
|
|
68
|
+
for (let i = 0; i < zeros; i++) out += "1";
|
|
69
|
+
for (; it < size; it++) out += ALPHABET[b58[it]];
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function bs58decode(str) {
|
|
73
|
+
if (str.length === 0) return new Uint8Array(0);
|
|
74
|
+
let zeros = 0;
|
|
75
|
+
while (zeros < str.length && str[zeros] === "1") zeros++;
|
|
76
|
+
const size = (str.length - zeros) * 733 / 1e3 + 1 | 0;
|
|
77
|
+
const b256 = new Uint8Array(size);
|
|
78
|
+
let length = 0;
|
|
79
|
+
for (let i = zeros; i < str.length; i++) {
|
|
80
|
+
const code = str.charCodeAt(i);
|
|
81
|
+
const carryIn = code < 128 ? ALPHABET_MAP[code] : -1;
|
|
82
|
+
if (carryIn < 0) throw new Error(`Invalid base58 character at index ${i}`);
|
|
83
|
+
let carry = carryIn;
|
|
84
|
+
let j = 0;
|
|
85
|
+
for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
86
|
+
carry += 58 * b256[k];
|
|
87
|
+
b256[k] = carry & 255;
|
|
88
|
+
carry >>= 8;
|
|
89
|
+
}
|
|
90
|
+
length = j;
|
|
91
|
+
}
|
|
92
|
+
let it = size - length;
|
|
93
|
+
while (it < size && b256[it] === 0) it++;
|
|
94
|
+
const out = new Uint8Array(zeros + (size - it));
|
|
95
|
+
let p = zeros;
|
|
96
|
+
while (it < size) out[p++] = b256[it++];
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
var Point = ed25519.ExtendedPoint;
|
|
100
|
+
var L = ed25519.CURVE.n;
|
|
101
|
+
function generateMetaKey() {
|
|
102
|
+
return metaFromSeed(randomBytes(32));
|
|
103
|
+
}
|
|
104
|
+
function metaFromSeed(seed) {
|
|
105
|
+
if (seed.length !== 32) {
|
|
106
|
+
throw new Error(`metaFromSeed: seed must be 32 bytes (got ${seed.length})`);
|
|
107
|
+
}
|
|
108
|
+
const h = sha512(seed);
|
|
109
|
+
const sb = new Uint8Array(h.slice(0, 32));
|
|
110
|
+
sb[0] &= 248;
|
|
111
|
+
sb[31] &= 127;
|
|
112
|
+
sb[31] |= 64;
|
|
113
|
+
const scalar = bytesToNumberLE(sb) % L;
|
|
114
|
+
const pub = Point.BASE.multiply(scalar).toRawBytes();
|
|
115
|
+
return { seed: new Uint8Array(seed), scalar, pub };
|
|
116
|
+
}
|
|
117
|
+
function pubFromScalar(scalar) {
|
|
118
|
+
return Point.BASE.multiply(scalar).toRawBytes();
|
|
119
|
+
}
|
|
120
|
+
var Point2 = ed25519.ExtendedPoint;
|
|
121
|
+
var L2 = ed25519.CURVE.n;
|
|
122
|
+
var VERSION_TAG = new TextEncoder().encode("notrace.v1.stealth");
|
|
123
|
+
function modL(n) {
|
|
124
|
+
let r = n % L2;
|
|
125
|
+
if (r < 0n) r += L2;
|
|
126
|
+
return r;
|
|
127
|
+
}
|
|
128
|
+
function hash512ModL(input) {
|
|
129
|
+
return modL(bytesToNumberLE(sha512(input)));
|
|
130
|
+
}
|
|
131
|
+
function deriveStealthSender(metaPubBytes) {
|
|
132
|
+
if (metaPubBytes.length !== 32) {
|
|
133
|
+
throw new Error(`deriveStealthSender: meta_pub must be 32 bytes (got ${metaPubBytes.length})`);
|
|
134
|
+
}
|
|
135
|
+
const eph = metaFromSeed(randomBytes(32));
|
|
136
|
+
const metaPt = Point2.fromHex(metaPubBytes);
|
|
137
|
+
const sharedBytes = metaPt.multiply(eph.scalar).toRawBytes();
|
|
138
|
+
const tweak = hash512ModL(
|
|
139
|
+
concatBytes(VERSION_TAG, sharedBytes, eph.pub, metaPubBytes)
|
|
140
|
+
);
|
|
141
|
+
const stealthPt = metaPt.add(Point2.BASE.multiply(tweak));
|
|
142
|
+
const stealthPub = stealthPt.toRawBytes();
|
|
143
|
+
return { stealthPub, ephPub: eph.pub };
|
|
144
|
+
}
|
|
145
|
+
function recoverStealth(metaScalar, metaPubBytes, ephPubBytes) {
|
|
146
|
+
if (ephPubBytes.length !== 32) {
|
|
147
|
+
throw new Error(`recoverStealth: eph_pub must be 32 bytes (got ${ephPubBytes.length})`);
|
|
148
|
+
}
|
|
149
|
+
if (metaPubBytes.length !== 32) {
|
|
150
|
+
throw new Error(`recoverStealth: meta_pub must be 32 bytes (got ${metaPubBytes.length})`);
|
|
151
|
+
}
|
|
152
|
+
const ephPt = Point2.fromHex(ephPubBytes);
|
|
153
|
+
const sharedBytes = ephPt.multiply(metaScalar).toRawBytes();
|
|
154
|
+
const tweak = hash512ModL(
|
|
155
|
+
concatBytes(VERSION_TAG, sharedBytes, ephPubBytes, metaPubBytes)
|
|
156
|
+
);
|
|
157
|
+
const stealthScalar = modL(metaScalar + tweak);
|
|
158
|
+
const stealthPub = Point2.BASE.multiply(stealthScalar).toRawBytes();
|
|
159
|
+
return { stealthScalar, stealthPub };
|
|
160
|
+
}
|
|
161
|
+
var Point3 = ed25519.ExtendedPoint;
|
|
162
|
+
var L3 = ed25519.CURVE.n;
|
|
163
|
+
function modL2(n) {
|
|
164
|
+
let r = n % L3;
|
|
165
|
+
if (r < 0n) r += L3;
|
|
166
|
+
return r;
|
|
167
|
+
}
|
|
168
|
+
function hash512ModL2(input) {
|
|
169
|
+
return modL2(bytesToNumberLE(sha512(input)));
|
|
170
|
+
}
|
|
171
|
+
function signWithScalar(scalar, pubBytes, message) {
|
|
172
|
+
const prefix = randomBytes(32);
|
|
173
|
+
const r = hash512ModL2(concatBytes(prefix, pubBytes, message));
|
|
174
|
+
const R = Point3.BASE.multiply(r).toRawBytes();
|
|
175
|
+
const k = hash512ModL2(concatBytes(R, pubBytes, message));
|
|
176
|
+
const S = modL2(r + k * scalar);
|
|
177
|
+
return concatBytes(R, numberToBytesLE(S, 32));
|
|
178
|
+
}
|
|
179
|
+
function verify(sig, message, pubBytes) {
|
|
180
|
+
return ed25519.verify(sig, message, pubBytes);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/memo.ts
|
|
184
|
+
var MEMO_PREFIX = "nt1:";
|
|
185
|
+
var MEMO_PROGRAM_ID = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
|
|
186
|
+
function encodeMemo(ephPubBytes) {
|
|
187
|
+
if (ephPubBytes.length !== 32) {
|
|
188
|
+
throw new Error(`encodeMemo: eph_pub must be 32 bytes (got ${ephPubBytes.length})`);
|
|
189
|
+
}
|
|
190
|
+
return MEMO_PREFIX + bs58encode(ephPubBytes);
|
|
191
|
+
}
|
|
192
|
+
function parseMemo(memoString) {
|
|
193
|
+
if (typeof memoString !== "string") return null;
|
|
194
|
+
if (!memoString.startsWith(MEMO_PREFIX)) return null;
|
|
195
|
+
const body = memoString.slice(MEMO_PREFIX.length).trim();
|
|
196
|
+
try {
|
|
197
|
+
const bytes = bs58decode(body);
|
|
198
|
+
if (bytes.length !== 32) return null;
|
|
199
|
+
return bytes;
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/payLink.ts
|
|
206
|
+
var DEFAULT_ORIGIN = "https://notracesol.xyz";
|
|
207
|
+
function makePayLink(metaPubBytes, origin = DEFAULT_ORIGIN) {
|
|
208
|
+
if (metaPubBytes.length !== 32) {
|
|
209
|
+
throw new Error(`makePayLink: meta_pub must be 32 bytes (got ${metaPubBytes.length})`);
|
|
210
|
+
}
|
|
211
|
+
const cleanOrigin = origin.replace(/\/+$/, "");
|
|
212
|
+
return `${cleanOrigin}/pay#m=${bs58encode(metaPubBytes)}`;
|
|
213
|
+
}
|
|
214
|
+
function parsePayLink(url) {
|
|
215
|
+
let parsed;
|
|
216
|
+
try {
|
|
217
|
+
parsed = new URL(url);
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
const fragMatch = parsed.hash.match(/(?:^#|&)m=([1-9A-HJ-NP-Za-km-z]+)/);
|
|
222
|
+
const queryMatch = parsed.searchParams.get("m");
|
|
223
|
+
const encoded = fragMatch?.[1] ?? queryMatch;
|
|
224
|
+
if (!encoded) return null;
|
|
225
|
+
try {
|
|
226
|
+
const bytes = bs58decode(encoded);
|
|
227
|
+
return bytes.length === 32 ? bytes : null;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/scan.ts
|
|
234
|
+
async function scanForPayments(meta, rpc, opts = {}) {
|
|
235
|
+
const limit = opts.limit ?? 100;
|
|
236
|
+
const concurrency = Math.max(1, opts.concurrency ?? 5);
|
|
237
|
+
const seen = opts.alreadyScanned ?? /* @__PURE__ */ new Set();
|
|
238
|
+
const sigs = await rpc.getSignaturesForAddress(MEMO_PROGRAM_ID, {
|
|
239
|
+
limit,
|
|
240
|
+
...opts.before ? { before: opts.before } : {}
|
|
241
|
+
});
|
|
242
|
+
const fresh = sigs.filter((s) => !seen.has(s.signature));
|
|
243
|
+
const matches = [];
|
|
244
|
+
for (let i = 0; i < fresh.length; i += concurrency) {
|
|
245
|
+
const batch = fresh.slice(i, i + concurrency);
|
|
246
|
+
const results = await Promise.allSettled(
|
|
247
|
+
batch.map((s) => checkSignature(s.signature, meta, rpc))
|
|
248
|
+
);
|
|
249
|
+
for (let j = 0; j < results.length; j++) {
|
|
250
|
+
const r = results[j];
|
|
251
|
+
const sig = batch[j].signature;
|
|
252
|
+
seen.add(sig);
|
|
253
|
+
if (r.status === "fulfilled" && r.value) matches.push(r.value);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return matches;
|
|
257
|
+
}
|
|
258
|
+
async function checkSignature(signature, meta, rpc) {
|
|
259
|
+
let tx;
|
|
260
|
+
try {
|
|
261
|
+
tx = await rpc.getParsedTransaction(signature, {
|
|
262
|
+
maxSupportedTransactionVersion: 0,
|
|
263
|
+
commitment: "confirmed"
|
|
264
|
+
});
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
if (!tx || tx.meta?.err) return null;
|
|
269
|
+
const instrs = tx.transaction.message.instructions || [];
|
|
270
|
+
let memoString = null;
|
|
271
|
+
for (const ix of instrs) {
|
|
272
|
+
const pid = typeof ix.programId === "string" ? ix.programId : ix.programId?.toString();
|
|
273
|
+
if (pid !== MEMO_PROGRAM_ID) continue;
|
|
274
|
+
if (typeof ix.parsed === "string") {
|
|
275
|
+
memoString = ix.parsed;
|
|
276
|
+
} else if (ix.parsed && typeof ix.parsed === "object" && typeof ix.parsed.info === "string") {
|
|
277
|
+
memoString = ix.parsed.info;
|
|
278
|
+
} else if (typeof ix.data === "string") {
|
|
279
|
+
try {
|
|
280
|
+
memoString = new TextDecoder().decode(bs58decode(ix.data));
|
|
281
|
+
} catch {
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (memoString) break;
|
|
285
|
+
}
|
|
286
|
+
if (!memoString) return null;
|
|
287
|
+
const ephPub = parseMemo(memoString);
|
|
288
|
+
if (!ephPub) return null;
|
|
289
|
+
const { stealthScalar, stealthPub } = recoverStealth(meta.scalar, meta.pub, ephPub);
|
|
290
|
+
const expectedAddr = bs58encode(stealthPub);
|
|
291
|
+
for (const ix of instrs) {
|
|
292
|
+
const parsed = ix.parsed;
|
|
293
|
+
if (!parsed || typeof parsed !== "object") continue;
|
|
294
|
+
if (parsed.type !== "transfer") continue;
|
|
295
|
+
const info = parsed.info;
|
|
296
|
+
if (!info || typeof info !== "object") continue;
|
|
297
|
+
if (info.destination !== expectedAddr) continue;
|
|
298
|
+
return {
|
|
299
|
+
signature,
|
|
300
|
+
slot: tx.slot,
|
|
301
|
+
blockTimeMs: tx.blockTime ? tx.blockTime * 1e3 : null,
|
|
302
|
+
lamports: typeof info.lamports === "number" ? info.lamports : 0,
|
|
303
|
+
source: info.source ?? "",
|
|
304
|
+
stealthPub: expectedAddr,
|
|
305
|
+
stealthScalar,
|
|
306
|
+
ephPub: bs58encode(ephPub)
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/index.ts
|
|
313
|
+
var VERSION = "1.0.0";
|
|
314
|
+
|
|
315
|
+
export { MEMO_PREFIX, MEMO_PROGRAM_ID, VERSION, bs58decode, bs58encode, bytesEqual, bytesToNumberLE, checkSignature, concatBytes, deriveStealthSender, encodeMemo, generateMetaKey, makePayLink, metaFromSeed, numberToBytesLE, parseMemo, parsePayLink, pubFromScalar, recoverStealth, scanForPayments, signWithScalar, verify };
|
|
316
|
+
//# sourceMappingURL=index.js.map
|
|
317
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bytes.ts","../src/base58.ts","../src/keys.ts","../src/stealth.ts","../src/sign.ts","../src/memo.ts","../src/payLink.ts","../src/scan.ts","../src/index.ts"],"names":["Point","ed25519","L","sha512","randomBytes","modL","hash512ModL"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Byte/scalar helpers shared across the SDK.\n *\n * Kept dependency-free — no `Buffer`, no `bigint-buffer`. Works in browser,\n * Node 18+, Deno, and Bun without polyfills.\n */\n\n/** Concatenate any number of byte arrays into a single Uint8Array. */\nexport function concatBytes(...arrs: Uint8Array[]): Uint8Array {\n let total = 0;\n for (const a of arrs) total += a.length;\n const out = new Uint8Array(total);\n let off = 0;\n for (const a of arrs) {\n out.set(a, off);\n off += a.length;\n }\n return out;\n}\n\n/** Little-endian unsigned integer decode. */\nexport function bytesToNumberLE(bytes: Uint8Array): bigint {\n let n = 0n;\n for (let i = bytes.length - 1; i >= 0; i--) {\n n = (n << 8n) | BigInt(bytes[i]!);\n }\n return n;\n}\n\n/** Little-endian unsigned integer encode to a fixed length. */\nexport function numberToBytesLE(n: bigint, len: number): Uint8Array {\n const out = new Uint8Array(len);\n let x = BigInt(n);\n for (let i = 0; i < len; i++) {\n out[i] = Number(x & 0xffn);\n x >>= 8n;\n }\n return out;\n}\n\n/** Compare two Uint8Arrays for byte equality. */\nexport function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n","/**\n * Minimal base58 (Bitcoin/Solana alphabet) encoder/decoder.\n *\n * Zero-dep. Identical output to `bs58` and `@solana/web3.js` Address encoding.\n */\n\nconst ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\";\n\nconst ALPHABET_MAP: Int8Array = (() => {\n const m = new Int8Array(128).fill(-1);\n for (let i = 0; i < ALPHABET.length; i++) m[ALPHABET.charCodeAt(i)] = i;\n return m;\n})();\n\n/** Encode a byte array to a base58 string. */\nexport function bs58encode(bytes: Uint8Array): string {\n if (bytes.length === 0) return \"\";\n let zeros = 0;\n while (zeros < bytes.length && bytes[zeros] === 0) zeros++;\n const size = ((bytes.length - zeros) * 138 / 100 + 1) | 0;\n const b58 = new Uint8Array(size);\n let length = 0;\n for (let i = zeros; i < bytes.length; i++) {\n let carry = bytes[i]!;\n let j = 0;\n for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {\n carry += 256 * b58[k]!;\n b58[k] = carry % 58;\n carry = (carry / 58) | 0;\n }\n length = j;\n }\n let it = size - length;\n while (it < size && b58[it] === 0) it++;\n let out = \"\";\n for (let i = 0; i < zeros; i++) out += \"1\";\n for (; it < size; it++) out += ALPHABET[b58[it]!];\n return out;\n}\n\n/** Decode a base58 string back to bytes. Throws on invalid input. */\nexport function bs58decode(str: string): Uint8Array {\n if (str.length === 0) return new Uint8Array(0);\n let zeros = 0;\n while (zeros < str.length && str[zeros] === \"1\") zeros++;\n const size = ((str.length - zeros) * 733 / 1000 + 1) | 0;\n const b256 = new Uint8Array(size);\n let length = 0;\n for (let i = zeros; i < str.length; i++) {\n const code = str.charCodeAt(i);\n const carryIn = code < 128 ? ALPHABET_MAP[code]! : -1;\n if (carryIn < 0) throw new Error(`Invalid base58 character at index ${i}`);\n let carry = carryIn;\n let j = 0;\n for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {\n carry += 58 * b256[k]!;\n b256[k] = carry & 0xff;\n carry >>= 8;\n }\n length = j;\n }\n let it = size - length;\n while (it < size && b256[it] === 0) it++;\n const out = new Uint8Array(zeros + (size - it));\n let p = zeros;\n while (it < size) out[p++] = b256[it++]!;\n return out;\n}\n","/**\n * Meta-keypair generation and seed derivation.\n *\n * A NoTrace identity is a single ed25519 keypair (the *meta-key*). Its public\n * half is shared with payers — that's enough for them to derive a one-time\n * stealth address. Its private half (the *view+spend scalar*) is what lets the\n * recipient detect and claim incoming payments.\n *\n * Note: this is a non-hierarchical key. If you want separate view-only and\n * spend keys, derive them upstream from your own master secret and pass the\n * spend scalar in only when you need to sign.\n */\n\nimport { ed25519 } from \"@noble/curves/ed25519\";\nimport { sha512 } from \"@noble/hashes/sha2\";\nimport { randomBytes } from \"@noble/hashes/utils\";\nimport { bytesToNumberLE } from \"./bytes.js\";\n\nconst Point = ed25519.ExtendedPoint;\nconst L = ed25519.CURVE.n;\n\n/** A meta-keypair. `seed` and `scalar` are *secret*; only `pub` is shareable. */\nexport interface MetaKey {\n /** Original 32-byte random seed (kept for backup/export). */\n seed: Uint8Array;\n /** ed25519 scalar derived from the seed (in [0, L)). */\n scalar: bigint;\n /** 32-byte compressed ed25519 public point. Share this with payers. */\n pub: Uint8Array;\n}\n\n/** Generate a fresh random meta-keypair using `crypto.getRandomValues`. */\nexport function generateMetaKey(): MetaKey {\n return metaFromSeed(randomBytes(32));\n}\n\n/**\n * Deterministically derive a meta-keypair from a 32-byte seed.\n *\n * Performs the standard ed25519 clamping (RFC 8032 §5.1.5) so the scalar lands\n * in a valid range. Same `seed` → same `pub` forever.\n */\nexport function metaFromSeed(seed: Uint8Array): MetaKey {\n if (seed.length !== 32) {\n throw new Error(`metaFromSeed: seed must be 32 bytes (got ${seed.length})`);\n }\n const h = sha512(seed);\n const sb = new Uint8Array(h.slice(0, 32));\n // ed25519 clamping\n sb[0]! &= 248;\n sb[31]! &= 127;\n sb[31]! |= 64;\n const scalar = bytesToNumberLE(sb) % L;\n const pub = Point.BASE.multiply(scalar).toRawBytes();\n // Defensive copy of `seed` so callers can't mutate our state.\n return { seed: new Uint8Array(seed), scalar, pub };\n}\n\n/** Recompute the public point for a given scalar. */\nexport function pubFromScalar(scalar: bigint): Uint8Array {\n return Point.BASE.multiply(scalar).toRawBytes();\n}\n","/**\n * Stealth-address derivation and recovery.\n *\n * The protocol is ECDH-on-ed25519 with a scalar-tweak — the same shape used by\n * ERC-5564 and Umbra Cash, ported to Solana's native curve. Properties:\n *\n * - Sender derives a stealth address from the recipient's published `meta_pub`.\n * - Sender CANNOT recover the stealth private key (would need `meta_scalar`).\n * - Recipient scans memos and applies their `meta_scalar` to detect payments.\n * - The stealth pub is a normal ed25519 point → a valid Solana address.\n *\n * The \"version tag\" included in the tweak makes the protocol forward-compatible:\n * a future curve swap or hash change can ship a new tag without breaking\n * existing wallets.\n */\n\nimport { ed25519 } from \"@noble/curves/ed25519\";\nimport { sha512 } from \"@noble/hashes/sha2\";\nimport { randomBytes } from \"@noble/hashes/utils\";\nimport { bytesToNumberLE, concatBytes } from \"./bytes.js\";\nimport { metaFromSeed } from \"./keys.js\";\n\nconst Point = ed25519.ExtendedPoint;\nconst L = ed25519.CURVE.n;\n\nconst VERSION_TAG = new TextEncoder().encode(\"notrace.v1.stealth\");\n\nfunction modL(n: bigint): bigint {\n let r = n % L;\n if (r < 0n) r += L;\n return r;\n}\n\nfunction hash512ModL(input: Uint8Array): bigint {\n return modL(bytesToNumberLE(sha512(input)));\n}\n\n/** Result of a sender-side derivation. */\nexport interface SenderDerivation {\n /** The one-time stealth address (32-byte ed25519 pub) to send funds to. */\n stealthPub: Uint8Array;\n /** The ephemeral pub the sender must publish on-chain (e.g. in a memo). */\n ephPub: Uint8Array;\n}\n\n/**\n * SENDER side: derive a one-time stealth address from the recipient's meta-pub.\n *\n * eph_scalar, eph_pub = fresh ed25519 keypair\n * shared = eph_scalar × meta_pub (DH on ed25519)\n * tweak = SHA512(ver ‖ shared ‖ eph_pub ‖ meta_pub) mod L\n * stealth_pub = meta_pub + tweak × G (point addition)\n *\n * The sender publishes `eph_pub`; only the holder of `meta_scalar` can both\n * recover `stealth_scalar` and produce signatures over it.\n *\n * Safe to call from a hostile environment — no state is persisted.\n */\nexport function deriveStealthSender(metaPubBytes: Uint8Array): SenderDerivation {\n if (metaPubBytes.length !== 32) {\n throw new Error(`deriveStealthSender: meta_pub must be 32 bytes (got ${metaPubBytes.length})`);\n }\n\n // Fresh ephemeral keypair via the same clamping path as the meta-key.\n const eph = metaFromSeed(randomBytes(32));\n\n // DH on ed25519\n const metaPt = Point.fromHex(metaPubBytes);\n const sharedBytes = metaPt.multiply(eph.scalar).toRawBytes();\n\n // Tweak. Include all four inputs so the tweak is bound to this exact pairing.\n const tweak = hash512ModL(\n concatBytes(VERSION_TAG, sharedBytes, eph.pub, metaPubBytes)\n );\n\n // stealth_pub = meta_pub + tweak * G\n const stealthPt = metaPt.add(Point.BASE.multiply(tweak));\n const stealthPub = stealthPt.toRawBytes();\n\n return { stealthPub, ephPub: eph.pub };\n}\n\n/** Result of a recipient-side recovery — pub matches what the sender derived. */\nexport interface RecipientRecovery {\n /** The recovered scalar — secret. Use this to sign sweeps. */\n stealthScalar: bigint;\n /** The recovered pub — compare against the on-chain destination. */\n stealthPub: Uint8Array;\n}\n\n/**\n * RECIPIENT side: given your meta-key and an ephemeral pub from a memo, recover\n * the stealth keypair the sender derived.\n *\n * shared = meta_scalar × eph_pub (same point as sender's)\n * tweak = SHA512(ver ‖ shared ‖ eph_pub ‖ meta_pub) mod L\n * stealth_scalar = (meta_scalar + tweak) mod L\n * stealth_pub = stealth_scalar × G\n *\n * Compare the returned `stealthPub` against the SOL/SPL `destination` field of\n * the transaction containing the memo. A match means the payment is yours.\n */\nexport function recoverStealth(\n metaScalar: bigint,\n metaPubBytes: Uint8Array,\n ephPubBytes: Uint8Array\n): RecipientRecovery {\n if (ephPubBytes.length !== 32) {\n throw new Error(`recoverStealth: eph_pub must be 32 bytes (got ${ephPubBytes.length})`);\n }\n if (metaPubBytes.length !== 32) {\n throw new Error(`recoverStealth: meta_pub must be 32 bytes (got ${metaPubBytes.length})`);\n }\n\n const ephPt = Point.fromHex(ephPubBytes);\n const sharedBytes = ephPt.multiply(metaScalar).toRawBytes();\n\n const tweak = hash512ModL(\n concatBytes(VERSION_TAG, sharedBytes, ephPubBytes, metaPubBytes)\n );\n const stealthScalar = modL(metaScalar + tweak);\n const stealthPub = Point.BASE.multiply(stealthScalar).toRawBytes();\n\n return { stealthScalar, stealthPub };\n}\n","/**\n * ed25519 signing with a raw scalar (not a seed).\n *\n * Standard ed25519 sign-from-seed first hashes the seed and uses the upper half\n * as a deterministic nonce. Our stealth scalars are *derived*, not seeded — so\n * we use a Schnorr-style construction directly:\n *\n * r = SHA512(prefix ‖ pub ‖ msg) mod L where prefix is 32 fresh bytes\n * R = r × G\n * k = SHA512(R ‖ pub ‖ msg) mod L\n * S = (r + k × scalar) mod L\n * sig = R ‖ S (64 bytes, verifiable as ed25519)\n *\n * The random `prefix` is a hedge against bad randomness leaking the scalar via\n * a colliding `r`. In normal NoTrace use each stealth scalar signs at most one\n * sweep, so even non-hedged would be safe — we hedge anyway.\n */\n\nimport { ed25519 } from \"@noble/curves/ed25519\";\nimport { sha512 } from \"@noble/hashes/sha2\";\nimport { randomBytes } from \"@noble/hashes/utils\";\nimport { bytesToNumberLE, concatBytes, numberToBytesLE } from \"./bytes.js\";\n\nconst Point = ed25519.ExtendedPoint;\nconst L = ed25519.CURVE.n;\n\nfunction modL(n: bigint): bigint {\n let r = n % L;\n if (r < 0n) r += L;\n return r;\n}\n\nfunction hash512ModL(input: Uint8Array): bigint {\n return modL(bytesToNumberLE(sha512(input)));\n}\n\n/**\n * Produce a 64-byte ed25519 signature using a raw scalar.\n *\n * The resulting signature verifies against `pub = scalar × G` under the\n * standard ed25519 verify algorithm (e.g. `ed25519.verify` in @noble/curves,\n * or Solana's runtime).\n */\nexport function signWithScalar(\n scalar: bigint,\n pubBytes: Uint8Array,\n message: Uint8Array\n): Uint8Array {\n const prefix = randomBytes(32);\n const r = hash512ModL(concatBytes(prefix, pubBytes, message));\n const R = Point.BASE.multiply(r).toRawBytes();\n const k = hash512ModL(concatBytes(R, pubBytes, message));\n const S = modL(r + k * scalar);\n return concatBytes(R, numberToBytesLE(S, 32));\n}\n\n/** Re-export `ed25519.verify` for callers that don't want to import @noble directly. */\nexport function verify(\n sig: Uint8Array,\n message: Uint8Array,\n pubBytes: Uint8Array\n): boolean {\n return ed25519.verify(sig, message, pubBytes);\n}\n","/**\n * Memo-program encoding for the ephemeral pub.\n *\n * Every NoTrace payment carries a memo of the form `nt1:<bs58_eph_pub>`. The\n * recipient's scanner parses these out of memo-program instructions and feeds\n * them to `recoverStealth` to test for matches.\n *\n * The `nt1:` prefix makes the protocol forward-compatible: a future scheme can\n * use `nt2:` and old wallets will simply skip those memos.\n */\n\nimport { bs58decode, bs58encode } from \"./base58.js\";\n\n/** Current memo prefix. Bump when the on-chain wire format changes. */\nexport const MEMO_PREFIX = \"nt1:\";\n\n/** Solana's official Memo Program address (v2). Useful for filtering RPC calls. */\nexport const MEMO_PROGRAM_ID = \"MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr\";\n\n/**\n * Encode an ephemeral public key into a memo string for on-chain publication.\n * Output is ~48 chars (`nt1:` + 44-char base58).\n */\nexport function encodeMemo(ephPubBytes: Uint8Array): string {\n if (ephPubBytes.length !== 32) {\n throw new Error(`encodeMemo: eph_pub must be 32 bytes (got ${ephPubBytes.length})`);\n }\n return MEMO_PREFIX + bs58encode(ephPubBytes);\n}\n\n/**\n * Parse a memo string back into the ephemeral pub. Returns `null` for any\n * memo that doesn't start with the current prefix or fails to decode to 32\n * bytes — so the scanner can pipe every memo through this without try/catch.\n */\nexport function parseMemo(memoString: unknown): Uint8Array | null {\n if (typeof memoString !== \"string\") return null;\n if (!memoString.startsWith(MEMO_PREFIX)) return null;\n const body = memoString.slice(MEMO_PREFIX.length).trim();\n try {\n const bytes = bs58decode(body);\n if (bytes.length !== 32) return null;\n return bytes;\n } catch {\n return null;\n }\n}\n","/**\n * NoTrace pay-link helpers.\n *\n * A pay-link encodes a recipient's `meta_pub` in the URL fragment of a hosted\n * `/pay` page (default: https://notracesol.xyz/pay). The recipient shares the\n * link; the payer's browser derives a one-time address client-side and signs\n * the transfer through Phantom.\n *\n * Format: `https://<origin>/pay#m=<bs58(meta_pub)>`\n *\n * Using the URL fragment (after `#`) means the meta-pub never reaches the\n * NoTrace server — even the request log can't link payer to recipient.\n */\n\nimport { bs58decode, bs58encode } from \"./base58.js\";\n\nconst DEFAULT_ORIGIN = \"https://notracesol.xyz\";\n\n/** Build a `/pay` link for a given meta-pub. */\nexport function makePayLink(metaPubBytes: Uint8Array, origin: string = DEFAULT_ORIGIN): string {\n if (metaPubBytes.length !== 32) {\n throw new Error(`makePayLink: meta_pub must be 32 bytes (got ${metaPubBytes.length})`);\n }\n const cleanOrigin = origin.replace(/\\/+$/, \"\");\n return `${cleanOrigin}/pay#m=${bs58encode(metaPubBytes)}`;\n}\n\n/**\n * Parse a NoTrace pay-link and return the recipient meta-pub.\n * Returns `null` for any input that isn't a parseable NoTrace pay-link.\n */\nexport function parsePayLink(url: string): Uint8Array | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return null;\n }\n // Accept both `#m=...` (fragment) and `?m=...` (query) for robustness.\n const fragMatch = parsed.hash.match(/(?:^#|&)m=([1-9A-HJ-NP-Za-km-z]+)/);\n const queryMatch = parsed.searchParams.get(\"m\");\n const encoded = fragMatch?.[1] ?? queryMatch;\n if (!encoded) return null;\n try {\n const bytes = bs58decode(encoded);\n return bytes.length === 32 ? bytes : null;\n } catch {\n return null;\n }\n}\n","/**\n * Memo-program scanner — find incoming stealth payments addressed to you.\n *\n * Standalone (no `@solana/web3.js` dependency at type-level): the scanner takes\n * a small `RpcLike` interface so it composes with any RPC client — a real\n * `Connection` from web3.js, a mock for tests, or a thin HTTP wrapper.\n *\n * Algorithm per scanned signature:\n *\n * 1. Fetch the parsed tx.\n * 2. Find a Memo-program instruction; extract the memo string.\n * 3. `parseMemo(...)` → 32-byte `eph_pub` (skip if not an `nt1:` memo).\n * 4. `recoverStealth(meta_scalar, meta_pub, eph_pub)` → expected `stealth_pub`.\n * 5. Look for a SystemProgram `transfer` whose `destination` matches; if so,\n * it's a payment to you.\n */\n\nimport { bs58decode, bs58encode } from \"./base58.js\";\nimport type { MetaKey } from \"./keys.js\";\nimport { MEMO_PROGRAM_ID, parseMemo } from \"./memo.js\";\nimport { recoverStealth } from \"./stealth.js\";\n\n/** Minimal shape we need from an RPC client. */\nexport interface RpcLike {\n /**\n * Return signatures touching `address`, newest first. Slot/blockTime are\n * optional but recommended for ordering and display.\n */\n getSignaturesForAddress(\n address: string,\n opts?: { limit?: number; before?: string; until?: string }\n ): Promise<Array<{ signature: string; slot?: number; blockTime?: number | null }>>;\n\n /**\n * Return a parsed transaction. The shape matches web3.js's\n * `getParsedTransaction` for the fields the scanner actually reads.\n */\n getParsedTransaction(\n signature: string,\n opts?: { maxSupportedTransactionVersion?: number; commitment?: string }\n ): Promise<ParsedTransactionLike | null>;\n}\n\n/** Minimal parsed-tx shape required by the scanner. */\nexport interface ParsedTransactionLike {\n slot?: number;\n blockTime?: number | null;\n meta?: { err?: unknown } | null;\n transaction: {\n message: {\n instructions: Array<ParsedInstructionLike>;\n };\n };\n}\n\nexport interface ParsedInstructionLike {\n programId?: { toString(): string } | string;\n /** Memo program may surface the memo here as a string. */\n parsed?:\n | string\n | {\n type?: string;\n info?: { destination?: string; source?: string; lamports?: number } | string;\n };\n /** Raw memo bytes (base58-encoded) if the program is not parsed. */\n data?: string;\n}\n\n/** A successfully-matched stealth payment. */\nexport interface StealthPayment {\n /** Transaction signature on Solana. */\n signature: string;\n /** Slot the tx landed in (if the RPC returned it). */\n slot?: number;\n /** Block timestamp in **ms** since epoch (or `null` if RPC didn't supply). */\n blockTimeMs?: number | null;\n /** Lamports received at the stealth address. */\n lamports: number;\n /** Source wallet (the payer). */\n source: string;\n /** Stealth address (base58). */\n stealthPub: string;\n /** Secret stealth scalar — needed to sign sweeps. Treat as private key. */\n stealthScalar: bigint;\n /** Ephemeral pub from the memo (base58). */\n ephPub: string;\n}\n\nexport interface ScanOptions {\n /** How many signatures to pull from the memo program per batch. Default 100. */\n limit?: number;\n /** Concurrent `getParsedTransaction` calls. Default 5 (RPC-friendly). */\n concurrency?: number;\n /** Cursor for pagination — fetch sigs older than this signature. */\n before?: string;\n /** Already-scanned signatures to skip. */\n alreadyScanned?: Set<string>;\n}\n\n/**\n * Scan the memo program for new stealth payments addressed to `meta`.\n *\n * Returns *only the matches*. Updates `alreadyScanned` (if provided) in-place\n * so subsequent calls can resume without re-checking.\n */\nexport async function scanForPayments(\n meta: Pick<MetaKey, \"scalar\" | \"pub\">,\n rpc: RpcLike,\n opts: ScanOptions = {}\n): Promise<StealthPayment[]> {\n const limit = opts.limit ?? 100;\n const concurrency = Math.max(1, opts.concurrency ?? 5);\n const seen = opts.alreadyScanned ?? new Set<string>();\n\n const sigs = await rpc.getSignaturesForAddress(MEMO_PROGRAM_ID, {\n limit,\n ...(opts.before ? { before: opts.before } : {}),\n });\n\n const fresh = sigs.filter((s) => !seen.has(s.signature));\n const matches: StealthPayment[] = [];\n\n for (let i = 0; i < fresh.length; i += concurrency) {\n const batch = fresh.slice(i, i + concurrency);\n const results = await Promise.allSettled(\n batch.map((s) => checkSignature(s.signature, meta, rpc))\n );\n for (let j = 0; j < results.length; j++) {\n const r = results[j]!;\n const sig = batch[j]!.signature;\n seen.add(sig);\n if (r.status === \"fulfilled\" && r.value) matches.push(r.value);\n }\n }\n\n return matches;\n}\n\n/**\n * Check a single signature for a stealth payment to `meta`. Returns the\n * payment if matched, `null` otherwise. Exposed so callers can wire their own\n * scanning loop (e.g. with subscribe/webhook flows instead of polling).\n */\nexport async function checkSignature(\n signature: string,\n meta: Pick<MetaKey, \"scalar\" | \"pub\">,\n rpc: RpcLike\n): Promise<StealthPayment | null> {\n let tx: ParsedTransactionLike | null;\n try {\n tx = await rpc.getParsedTransaction(signature, {\n maxSupportedTransactionVersion: 0,\n commitment: \"confirmed\",\n });\n } catch {\n return null;\n }\n if (!tx || tx.meta?.err) return null;\n\n const instrs = tx.transaction.message.instructions || [];\n\n // 1. Find the memo string.\n let memoString: string | null = null;\n for (const ix of instrs) {\n const pid = typeof ix.programId === \"string\" ? ix.programId : ix.programId?.toString();\n if (pid !== MEMO_PROGRAM_ID) continue;\n\n if (typeof ix.parsed === \"string\") {\n memoString = ix.parsed;\n } else if (ix.parsed && typeof ix.parsed === \"object\" && typeof ix.parsed.info === \"string\") {\n memoString = ix.parsed.info;\n } else if (typeof ix.data === \"string\") {\n try {\n memoString = new TextDecoder().decode(bs58decode(ix.data));\n } catch {\n /* ignore unparseable memo */\n }\n }\n if (memoString) break;\n }\n if (!memoString) return null;\n\n const ephPub = parseMemo(memoString);\n if (!ephPub) return null;\n\n // 2. Recover the expected stealth address.\n const { stealthScalar, stealthPub } = recoverStealth(meta.scalar, meta.pub, ephPub);\n const expectedAddr = bs58encode(stealthPub);\n\n // 3. Find a transfer to that address.\n for (const ix of instrs) {\n const parsed = ix.parsed;\n if (!parsed || typeof parsed !== \"object\") continue;\n if (parsed.type !== \"transfer\") continue;\n const info = parsed.info;\n if (!info || typeof info !== \"object\") continue;\n if (info.destination !== expectedAddr) continue;\n\n return {\n signature,\n slot: tx.slot,\n blockTimeMs: tx.blockTime ? tx.blockTime * 1000 : null,\n lamports: typeof info.lamports === \"number\" ? info.lamports : 0,\n source: info.source ?? \"\",\n stealthPub: expectedAddr,\n stealthScalar,\n ephPub: bs58encode(ephPub),\n };\n }\n\n return null;\n}\n","/**\n * @notrace/stealth-sdk\n *\n * Real stealth addresses on Solana. ECDH-derived one-time addresses on\n * ed25519, view-key recoverable, with a Memo-program footprint.\n *\n * Extracted from the production NoTrace wallet (https://notracesol.xyz).\n *\n * Quick start:\n *\n * import {\n * generateMetaKey, deriveStealthSender, recoverStealth,\n * encodeMemo, parseMemo, makePayLink, scanForPayments,\n * } from \"@notrace/stealth-sdk\";\n *\n * See README.md for end-to-end examples.\n */\n\n// Byte helpers (useful for callers building their own pipelines).\nexport { bytesEqual, bytesToNumberLE, concatBytes, numberToBytesLE } from \"./bytes.js\";\n\n// Base58 (Solana's address alphabet).\nexport { bs58decode, bs58encode } from \"./base58.js\";\n\n// Meta-keypair generation and derivation.\nexport type { MetaKey } from \"./keys.js\";\nexport { generateMetaKey, metaFromSeed, pubFromScalar } from \"./keys.js\";\n\n// Stealth derivation (sender) and recovery (recipient).\nexport type { RecipientRecovery, SenderDerivation } from \"./stealth.js\";\nexport { deriveStealthSender, recoverStealth } from \"./stealth.js\";\n\n// Signing — produce ed25519 sigs from a raw stealth scalar.\nexport { signWithScalar, verify } from \"./sign.js\";\n\n// Memo encoding for on-chain publication of the ephemeral pub.\nexport { MEMO_PREFIX, MEMO_PROGRAM_ID, encodeMemo, parseMemo } from \"./memo.js\";\n\n// Pay-link helpers.\nexport { makePayLink, parsePayLink } from \"./payLink.js\";\n\n// Inbox scanner — find payments addressed to your meta-key.\nexport type {\n ParsedInstructionLike,\n ParsedTransactionLike,\n RpcLike,\n ScanOptions,\n StealthPayment,\n} from \"./scan.js\";\nexport { checkSignature, scanForPayments } from \"./scan.js\";\n\n/** Package version, kept in sync with `package.json`. */\nexport const VERSION = \"1.0.0\";\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@notrace/stealth-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Real stealth addresses on Solana. ECDH-derived one-time addresses on ed25519, view-key recoverable, with a Memo-program footprint. Extracted from the production NoTrace wallet.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"solana",
|
|
7
|
+
"stealth-address",
|
|
8
|
+
"ecdh",
|
|
9
|
+
"ed25519",
|
|
10
|
+
"privacy",
|
|
11
|
+
"crypto",
|
|
12
|
+
"wallet"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://notracesol.xyz",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/NoTraceSol/stealth-sdk.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": "https://github.com/NoTraceSol/stealth-sdk/issues",
|
|
20
|
+
"author": "NoTrace",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"src",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@noble/curves": "^1.6.0",
|
|
48
|
+
"@noble/hashes": "^1.5.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@solana/web3.js": "^1.95.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@solana/web3.js": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@solana/web3.js": "^1.95.3",
|
|
60
|
+
"tsup": "^8.3.0",
|
|
61
|
+
"typescript": "^5.6.0",
|
|
62
|
+
"vitest": "^2.1.0"
|
|
63
|
+
},
|
|
64
|
+
"engines": {
|
|
65
|
+
"node": ">=18"
|
|
66
|
+
},
|
|
67
|
+
"sideEffects": false,
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/base58.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal base58 (Bitcoin/Solana alphabet) encoder/decoder.
|
|
3
|
+
*
|
|
4
|
+
* Zero-dep. Identical output to `bs58` and `@solana/web3.js` Address encoding.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
8
|
+
|
|
9
|
+
const ALPHABET_MAP: Int8Array = (() => {
|
|
10
|
+
const m = new Int8Array(128).fill(-1);
|
|
11
|
+
for (let i = 0; i < ALPHABET.length; i++) m[ALPHABET.charCodeAt(i)] = i;
|
|
12
|
+
return m;
|
|
13
|
+
})();
|
|
14
|
+
|
|
15
|
+
/** Encode a byte array to a base58 string. */
|
|
16
|
+
export function bs58encode(bytes: Uint8Array): string {
|
|
17
|
+
if (bytes.length === 0) return "";
|
|
18
|
+
let zeros = 0;
|
|
19
|
+
while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
|
|
20
|
+
const size = ((bytes.length - zeros) * 138 / 100 + 1) | 0;
|
|
21
|
+
const b58 = new Uint8Array(size);
|
|
22
|
+
let length = 0;
|
|
23
|
+
for (let i = zeros; i < bytes.length; i++) {
|
|
24
|
+
let carry = bytes[i]!;
|
|
25
|
+
let j = 0;
|
|
26
|
+
for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
27
|
+
carry += 256 * b58[k]!;
|
|
28
|
+
b58[k] = carry % 58;
|
|
29
|
+
carry = (carry / 58) | 0;
|
|
30
|
+
}
|
|
31
|
+
length = j;
|
|
32
|
+
}
|
|
33
|
+
let it = size - length;
|
|
34
|
+
while (it < size && b58[it] === 0) it++;
|
|
35
|
+
let out = "";
|
|
36
|
+
for (let i = 0; i < zeros; i++) out += "1";
|
|
37
|
+
for (; it < size; it++) out += ALPHABET[b58[it]!];
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Decode a base58 string back to bytes. Throws on invalid input. */
|
|
42
|
+
export function bs58decode(str: string): Uint8Array {
|
|
43
|
+
if (str.length === 0) return new Uint8Array(0);
|
|
44
|
+
let zeros = 0;
|
|
45
|
+
while (zeros < str.length && str[zeros] === "1") zeros++;
|
|
46
|
+
const size = ((str.length - zeros) * 733 / 1000 + 1) | 0;
|
|
47
|
+
const b256 = new Uint8Array(size);
|
|
48
|
+
let length = 0;
|
|
49
|
+
for (let i = zeros; i < str.length; i++) {
|
|
50
|
+
const code = str.charCodeAt(i);
|
|
51
|
+
const carryIn = code < 128 ? ALPHABET_MAP[code]! : -1;
|
|
52
|
+
if (carryIn < 0) throw new Error(`Invalid base58 character at index ${i}`);
|
|
53
|
+
let carry = carryIn;
|
|
54
|
+
let j = 0;
|
|
55
|
+
for (let k = size - 1; (carry !== 0 || j < length) && k >= 0; k--, j++) {
|
|
56
|
+
carry += 58 * b256[k]!;
|
|
57
|
+
b256[k] = carry & 0xff;
|
|
58
|
+
carry >>= 8;
|
|
59
|
+
}
|
|
60
|
+
length = j;
|
|
61
|
+
}
|
|
62
|
+
let it = size - length;
|
|
63
|
+
while (it < size && b256[it] === 0) it++;
|
|
64
|
+
const out = new Uint8Array(zeros + (size - it));
|
|
65
|
+
let p = zeros;
|
|
66
|
+
while (it < size) out[p++] = b256[it++]!;
|
|
67
|
+
return out;
|
|
68
|
+
}
|
package/src/bytes.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Byte/scalar helpers shared across the SDK.
|
|
3
|
+
*
|
|
4
|
+
* Kept dependency-free — no `Buffer`, no `bigint-buffer`. Works in browser,
|
|
5
|
+
* Node 18+, Deno, and Bun without polyfills.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Concatenate any number of byte arrays into a single Uint8Array. */
|
|
9
|
+
export function concatBytes(...arrs: Uint8Array[]): Uint8Array {
|
|
10
|
+
let total = 0;
|
|
11
|
+
for (const a of arrs) total += a.length;
|
|
12
|
+
const out = new Uint8Array(total);
|
|
13
|
+
let off = 0;
|
|
14
|
+
for (const a of arrs) {
|
|
15
|
+
out.set(a, off);
|
|
16
|
+
off += a.length;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Little-endian unsigned integer decode. */
|
|
22
|
+
export function bytesToNumberLE(bytes: Uint8Array): bigint {
|
|
23
|
+
let n = 0n;
|
|
24
|
+
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
25
|
+
n = (n << 8n) | BigInt(bytes[i]!);
|
|
26
|
+
}
|
|
27
|
+
return n;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Little-endian unsigned integer encode to a fixed length. */
|
|
31
|
+
export function numberToBytesLE(n: bigint, len: number): Uint8Array {
|
|
32
|
+
const out = new Uint8Array(len);
|
|
33
|
+
let x = BigInt(n);
|
|
34
|
+
for (let i = 0; i < len; i++) {
|
|
35
|
+
out[i] = Number(x & 0xffn);
|
|
36
|
+
x >>= 8n;
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Compare two Uint8Arrays for byte equality. */
|
|
42
|
+
export function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
43
|
+
if (a.length !== b.length) return false;
|
|
44
|
+
for (let i = 0; i < a.length; i++) {
|
|
45
|
+
if (a[i] !== b[i]) return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @notrace/stealth-sdk
|
|
3
|
+
*
|
|
4
|
+
* Real stealth addresses on Solana. ECDH-derived one-time addresses on
|
|
5
|
+
* ed25519, view-key recoverable, with a Memo-program footprint.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from the production NoTrace wallet (https://notracesol.xyz).
|
|
8
|
+
*
|
|
9
|
+
* Quick start:
|
|
10
|
+
*
|
|
11
|
+
* import {
|
|
12
|
+
* generateMetaKey, deriveStealthSender, recoverStealth,
|
|
13
|
+
* encodeMemo, parseMemo, makePayLink, scanForPayments,
|
|
14
|
+
* } from "@notrace/stealth-sdk";
|
|
15
|
+
*
|
|
16
|
+
* See README.md for end-to-end examples.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Byte helpers (useful for callers building their own pipelines).
|
|
20
|
+
export { bytesEqual, bytesToNumberLE, concatBytes, numberToBytesLE } from "./bytes.js";
|
|
21
|
+
|
|
22
|
+
// Base58 (Solana's address alphabet).
|
|
23
|
+
export { bs58decode, bs58encode } from "./base58.js";
|
|
24
|
+
|
|
25
|
+
// Meta-keypair generation and derivation.
|
|
26
|
+
export type { MetaKey } from "./keys.js";
|
|
27
|
+
export { generateMetaKey, metaFromSeed, pubFromScalar } from "./keys.js";
|
|
28
|
+
|
|
29
|
+
// Stealth derivation (sender) and recovery (recipient).
|
|
30
|
+
export type { RecipientRecovery, SenderDerivation } from "./stealth.js";
|
|
31
|
+
export { deriveStealthSender, recoverStealth } from "./stealth.js";
|
|
32
|
+
|
|
33
|
+
// Signing — produce ed25519 sigs from a raw stealth scalar.
|
|
34
|
+
export { signWithScalar, verify } from "./sign.js";
|
|
35
|
+
|
|
36
|
+
// Memo encoding for on-chain publication of the ephemeral pub.
|
|
37
|
+
export { MEMO_PREFIX, MEMO_PROGRAM_ID, encodeMemo, parseMemo } from "./memo.js";
|
|
38
|
+
|
|
39
|
+
// Pay-link helpers.
|
|
40
|
+
export { makePayLink, parsePayLink } from "./payLink.js";
|
|
41
|
+
|
|
42
|
+
// Inbox scanner — find payments addressed to your meta-key.
|
|
43
|
+
export type {
|
|
44
|
+
ParsedInstructionLike,
|
|
45
|
+
ParsedTransactionLike,
|
|
46
|
+
RpcLike,
|
|
47
|
+
ScanOptions,
|
|
48
|
+
StealthPayment,
|
|
49
|
+
} from "./scan.js";
|
|
50
|
+
export { checkSignature, scanForPayments } from "./scan.js";
|
|
51
|
+
|
|
52
|
+
/** Package version, kept in sync with `package.json`. */
|
|
53
|
+
export const VERSION = "1.0.0";
|