@zerc20/storage-sdk 0.0.0-stage → 0.1.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 +24 -0
- package/README.md +14 -2
- package/dist/candid.d.ts +73 -0
- package/dist/candid.js +51 -0
- package/dist/candid.js.map +1 -0
- package/dist/canister.d.ts +33 -0
- package/dist/canister.js +76 -0
- package/dist/canister.js.map +1 -0
- package/dist/crypto.d.ts +27 -0
- package/dist/crypto.js +99 -0
- package/dist/crypto.js.map +1 -0
- package/dist/formats.d.ts +202 -0
- package/dist/formats.js +238 -0
- package/dist/formats.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/powWorker.d.ts +1 -0
- package/dist/powWorker.js +4 -0
- package/dist/powWorker.js.map +1 -0
- package/dist/send.d.ts +51 -0
- package/dist/send.js +162 -0
- package/dist/send.js.map +1 -0
- package/dist/sync.d.ts +82 -0
- package/dist/sync.js +263 -0
- package/dist/sync.js.map +1 -0
- package/dist/viewKey.d.ts +45 -0
- package/dist/viewKey.js +104 -0
- package/dist/viewKey.js.map +1 -0
- package/dist/wire.d.ts +33 -0
- package/dist/wire.js +112 -0
- package/dist/wire.js.map +1 -0
- package/package.json +51 -4
- package/src/candid.ts +116 -0
- package/src/canister.ts +85 -0
- package/src/crypto.ts +113 -0
- package/src/formats.ts +399 -0
- package/src/index.ts +86 -0
- package/src/powWorker.ts +6 -0
- package/src/send.ts +224 -0
- package/src/sync.ts +376 -0
- package/src/viewKey.ts +152 -0
- package/src/wire.ts +136 -0
package/dist/sync.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// Incremental history. Everything is append-only or write-once, so a state remembers how far each
|
|
2
|
+
// list has been read and every refresh reads only past that. New records are found through the
|
|
3
|
+
// canister's log of lane slot ids: a refresh matches the next id of each of its lanes against the
|
|
4
|
+
// log's new entries, so its cost does not grow with the number of channels or receive codes.
|
|
5
|
+
import { concat, equal, hex, kdf, open, openFrom, seal, unhex, utf8 } from './crypto.js';
|
|
6
|
+
import { addressInbox, channelKey, channelSeed, codeKey, outgoingId, outgoingKey, ownKey, posKey, readChannel, readLaneRecord, readTarget, sealKey, slotId, } from './formats.js';
|
|
7
|
+
import { notifyKey, notifySecret } from './viewKey.js';
|
|
8
|
+
import { MAX_LOG_READ, MAX_READ } from './wire.js';
|
|
9
|
+
/** Slots read per sequence and round trip once a sequence has news. */
|
|
10
|
+
const BATCH = 16;
|
|
11
|
+
export const emptyState = (view, chains) => ({
|
|
12
|
+
address: view.address,
|
|
13
|
+
chains,
|
|
14
|
+
addressInboxRead: 0,
|
|
15
|
+
incoming: [],
|
|
16
|
+
outgoing: { next: 0, lanes: [] },
|
|
17
|
+
own: [],
|
|
18
|
+
log: null,
|
|
19
|
+
});
|
|
20
|
+
export function checkState(view, state) {
|
|
21
|
+
if (!equal(state.address, view.address))
|
|
22
|
+
throw new Error('the state belongs to another view key');
|
|
23
|
+
}
|
|
24
|
+
const payerOf = (r) => (r.kind === 'note' ? (r.payer ?? null) : null);
|
|
25
|
+
/** Reads what is new since `state` and returns the extended state, plus the records it found. `state` itself is
|
|
26
|
+
* left untouched, so a failed refresh loses nothing. A first sync is a refresh of `emptyState`.
|
|
27
|
+
*
|
|
28
|
+
* Queries only spot news: whatever is kept is read again as update calls, so a single lying replica
|
|
29
|
+
* can neither plant entries nor make a cursor skip one. With nothing new it costs queries only: one
|
|
30
|
+
* for outgoing records, one for the address inbox, and one for the lane log.
|
|
31
|
+
*
|
|
32
|
+
* A replica can still leave log entries out, which only delays those records: `full` reads every
|
|
33
|
+
* lane instead (one query per 256 of them: every channel, point of sale and the own lane on every
|
|
34
|
+
* watched chain, every code on its own), as the first refresh of a state does. Do that now and then, say hourly. */
|
|
35
|
+
export async function refresh(client, view, state, opts = {}) {
|
|
36
|
+
checkState(view, state);
|
|
37
|
+
const next = structuredClone(state);
|
|
38
|
+
// read before anything else: writes made while this refresh runs are found by the next one
|
|
39
|
+
const fromLog = opts.full || next.log === null ? null : next.log;
|
|
40
|
+
const log = fromLog === null ? { end: Number(await client.actor.log_length()), ids: [] } : await readLog(client, fromLog);
|
|
41
|
+
const news = new Set(log.ids.map((id) => hex(id)));
|
|
42
|
+
const { address, seed } = view;
|
|
43
|
+
const added = [];
|
|
44
|
+
/** The sequences of `sources` with news: a lane found in this refresh, or a chain just watched, may hold records
|
|
45
|
+
* from before the log position. */
|
|
46
|
+
const due = (sources) => sources.flatMap((source) => {
|
|
47
|
+
const via = source.lane?.via;
|
|
48
|
+
const chainIds = via?.type === 'code'
|
|
49
|
+
? next.chains.filter((c) => c === ('code' in via ? via.code.chainId : via.chainId))
|
|
50
|
+
: next.chains;
|
|
51
|
+
return chainIds.flatMap((chainId) => {
|
|
52
|
+
let chain = source.chains.find((c) => c.chainId === chainId);
|
|
53
|
+
const fresh = !chain;
|
|
54
|
+
if (!chain)
|
|
55
|
+
source.chains.push((chain = { chainId, nextSlotIndex: 0, records: [] }));
|
|
56
|
+
const idAt = (i) => slotId(source.key, chainId, i);
|
|
57
|
+
const isDue = fromLog === null || fresh || news.has(hex(idAt(chain.nextSlotIndex)));
|
|
58
|
+
return isDue ? [{ source, chain, next: chain.nextSlotIndex, idAt }] : [];
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
/** Takes what `sequences` held, every filled slot counting (so that a code's payer writes past the other payers'
|
|
62
|
+
* notes), and returns the records it took. */
|
|
63
|
+
const take = (sequences, found) => sequences.flatMap(({ source, chain }, s) => found[s].flatMap(({ id, slot }) => {
|
|
64
|
+
const slotIndex = chain.nextSlotIndex++;
|
|
65
|
+
const plain = open(sealKey(source.key), id, slot.data);
|
|
66
|
+
const record = plain && readLaneRecord(source.kind, chain.chainId, slotIndex, slot.at_ns, plain);
|
|
67
|
+
const peer = record && source.peerOf(record);
|
|
68
|
+
if (!record || peer === undefined)
|
|
69
|
+
return [];
|
|
70
|
+
chain.records.push(record);
|
|
71
|
+
added.push({ direction: source.direction, lane: source.lane, peer, record });
|
|
72
|
+
return [record];
|
|
73
|
+
}));
|
|
74
|
+
// The own lane and outgoing records past the cursors, perhaps written by another device. The own lane comes
|
|
75
|
+
// first, so that the codes of a new burn block, and a new point of sale's lane, are read in this refresh.
|
|
76
|
+
const own = {
|
|
77
|
+
direction: 'own',
|
|
78
|
+
kind: 'own',
|
|
79
|
+
lane: null,
|
|
80
|
+
chains: next.own,
|
|
81
|
+
key: ownKey(seed),
|
|
82
|
+
peerOf: () => null,
|
|
83
|
+
};
|
|
84
|
+
const ownSequences = due([own]);
|
|
85
|
+
const [ownFound, records] = await Promise.all([
|
|
86
|
+
readSequences(client, { Lane: null }, ownSequences),
|
|
87
|
+
readSequences(client, { Outgoing: null }, [{ next: next.outgoing.next, idAt: (i) => outgoingId(seed, i) }]),
|
|
88
|
+
]);
|
|
89
|
+
// two devices may record the same index, or the same point of sale: one lane each
|
|
90
|
+
const lanesHeld = new Set(next.incoming.filter((c) => c.via.type !== 'address').map((c) => hex(c.key)));
|
|
91
|
+
const hold = (via, key) => {
|
|
92
|
+
if (lanesHeld.has(hex(key)))
|
|
93
|
+
return;
|
|
94
|
+
lanesHeld.add(hex(key));
|
|
95
|
+
next.incoming.push({ peer: null, via, seed: null, inbox: null, key, chains: [] });
|
|
96
|
+
};
|
|
97
|
+
for (const r of take(ownSequences, ownFound)) {
|
|
98
|
+
if (r.kind === 'pos-lane')
|
|
99
|
+
hold({ type: 'pos', n: r.n }, posKey(seed, r.n));
|
|
100
|
+
if (r.kind !== 'burn-block')
|
|
101
|
+
continue;
|
|
102
|
+
r.salts.forEach((_, i) => {
|
|
103
|
+
const index = r.first + i;
|
|
104
|
+
hold({ type: 'code', chainId: r.chainId, tokenId: r.tokenId, index }, codeKey(seed, r.chainId, r.tokenId, index));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
for (const { id, slot } of records[0]) {
|
|
108
|
+
next.outgoing.next++;
|
|
109
|
+
const target = readTarget(open(outgoingKey(seed), id, slot.data) ?? new Uint8Array());
|
|
110
|
+
if (!target)
|
|
111
|
+
continue;
|
|
112
|
+
if (!('address' in target)) {
|
|
113
|
+
next.outgoing.lanes.push({
|
|
114
|
+
peer: null,
|
|
115
|
+
via: { type: 'code', code: target },
|
|
116
|
+
seed: null,
|
|
117
|
+
inbox: null,
|
|
118
|
+
key: target.key,
|
|
119
|
+
chains: [],
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const inbox = addressInbox(target.address, target.notifyKey);
|
|
124
|
+
const c = channelSeed(seed, address, inbox);
|
|
125
|
+
const channel = {
|
|
126
|
+
peer: target.address,
|
|
127
|
+
via: { type: 'address' },
|
|
128
|
+
seed: c,
|
|
129
|
+
inbox,
|
|
130
|
+
key: channelKey(c, inbox, address),
|
|
131
|
+
chains: [],
|
|
132
|
+
};
|
|
133
|
+
next.outgoing.lanes.push(channel);
|
|
134
|
+
}
|
|
135
|
+
// New announcements in the address inbox, once per seed. Deduplicating by payer instead would let a forged entry
|
|
136
|
+
// naming a real payer hide the real channel.
|
|
137
|
+
const inbox = addressInbox(address, notifyKey(view));
|
|
138
|
+
const infos = await readInbox(client, inbox, next.addressInboxRead);
|
|
139
|
+
next.addressInboxRead += infos.length;
|
|
140
|
+
for (const info of infos) {
|
|
141
|
+
const announced = readChannel(openFrom(notifySecret(view), 'announce', inbox, info) ?? new Uint8Array());
|
|
142
|
+
if (!announced || next.incoming.some((channel) => channel.seed && equal(channel.seed, announced.seed)))
|
|
143
|
+
continue;
|
|
144
|
+
const { payer, seed: c } = announced;
|
|
145
|
+
next.incoming.push({
|
|
146
|
+
peer: payer,
|
|
147
|
+
via: { type: 'address' },
|
|
148
|
+
seed: c,
|
|
149
|
+
inbox,
|
|
150
|
+
key: channelKey(c, inbox, payer),
|
|
151
|
+
chains: [],
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// New records in every channel and point of sale on every watched chain, and in every code on its own. Of a code's
|
|
155
|
+
// lane, the owner takes every payer's notes, and a payer only its own.
|
|
156
|
+
const sources = [
|
|
157
|
+
...next.incoming.map((lane) => ({
|
|
158
|
+
direction: 'incoming',
|
|
159
|
+
kind: lane.via.type === 'address' ? 'channel' : lane.via.type,
|
|
160
|
+
lane,
|
|
161
|
+
chains: lane.chains,
|
|
162
|
+
key: lane.key,
|
|
163
|
+
peerOf: (r) => (lane.via.type === 'code' ? payerOf(r) : lane.peer),
|
|
164
|
+
})),
|
|
165
|
+
...next.outgoing.lanes.map((lane) => ({
|
|
166
|
+
direction: 'outgoing',
|
|
167
|
+
kind: lane.via.type === 'code' ? 'code' : 'channel',
|
|
168
|
+
lane,
|
|
169
|
+
chains: lane.chains,
|
|
170
|
+
key: lane.key,
|
|
171
|
+
peerOf: (r) => (lane.via.type !== 'code' ? lane.peer : equal(payerOf(r), address) ? null : undefined),
|
|
172
|
+
})),
|
|
173
|
+
];
|
|
174
|
+
const sequences = due(sources);
|
|
175
|
+
take(sequences, await readSequences(client, { Lane: null }, sequences));
|
|
176
|
+
next.log = log.end;
|
|
177
|
+
return { state: next, added };
|
|
178
|
+
}
|
|
179
|
+
/** The lane log from `start` to its end. */
|
|
180
|
+
async function readLog(client, start) {
|
|
181
|
+
const ids = [];
|
|
182
|
+
for (;;) {
|
|
183
|
+
const page = await client.actor.get_log(BigInt(start + ids.length), MAX_LOG_READ);
|
|
184
|
+
ids.push(...page);
|
|
185
|
+
if (page.length < MAX_LOG_READ)
|
|
186
|
+
return { end: start + ids.length, ids };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Announcements from `start` on. */
|
|
190
|
+
async function readInbox(client, inbox, start) {
|
|
191
|
+
const infos = [];
|
|
192
|
+
for (;;) {
|
|
193
|
+
const at = start + infos.length;
|
|
194
|
+
const peek = await client.actor.get_announcements(inbox, at, MAX_READ);
|
|
195
|
+
if (peek.length === 0)
|
|
196
|
+
return infos;
|
|
197
|
+
const page = await client.replicated.get_announcements(inbox, at, MAX_READ);
|
|
198
|
+
infos.push(...page);
|
|
199
|
+
if (page.length < MAX_READ)
|
|
200
|
+
return infos;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** The filled slots of each sequence from its `next` index up to the first empty one. The first round
|
|
204
|
+
* checks one slot per sequence; sequences with news are then read `BATCH` slots at a time. */
|
|
205
|
+
async function readSequences(client, kind, sequences) {
|
|
206
|
+
const found = sequences.map(() => []);
|
|
207
|
+
let pending = sequences.map((_, s) => s);
|
|
208
|
+
for (let width = 1; pending.length > 0; width = BATCH) {
|
|
209
|
+
const ids = pending.map((s) => {
|
|
210
|
+
const { next, idAt } = sequences[s];
|
|
211
|
+
return Array.from({ length: width }, (_, i) => idAt(next + found[s].length + i));
|
|
212
|
+
});
|
|
213
|
+
const peek = await getSlots(client.actor, kind, ids.flat());
|
|
214
|
+
const news = ids.map((row, p) => row.slice(0, leading(peek.slice(p * width, (p + 1) * width))));
|
|
215
|
+
const confirmed = await getSlots(client.replicated, kind, news.flat());
|
|
216
|
+
let at = 0;
|
|
217
|
+
pending = pending.filter((s, p) => {
|
|
218
|
+
const slots = confirmed.slice(at, (at += news[p].length));
|
|
219
|
+
const n = leading(slots);
|
|
220
|
+
found[s].push(...slots.slice(0, n).map((slot, i) => ({ id: news[p][i], slot: slot })));
|
|
221
|
+
return n === width;
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return found;
|
|
225
|
+
}
|
|
226
|
+
/** `get_slots` in reads of at most `MAX_READ` ids. */
|
|
227
|
+
async function getSlots(actor, kind, ids) {
|
|
228
|
+
const reads = [];
|
|
229
|
+
for (let i = 0; i < ids.length; i += MAX_READ)
|
|
230
|
+
reads.push(actor.get_slots(kind, ids.slice(i, i + MAX_READ)));
|
|
231
|
+
return (await Promise.all(reads)).flat().map((slot) => slot[0] ?? null);
|
|
232
|
+
}
|
|
233
|
+
/** The first empty slot of a lane's sequence from slot `from` on: where a code's first payment goes, or a point of
|
|
234
|
+
* sale's next block. */
|
|
235
|
+
export async function sequenceEnd(client, idAt, from = 0) {
|
|
236
|
+
return from + (await readSequences(client, { Lane: null }, [{ next: from, idAt }]))[0].length;
|
|
237
|
+
}
|
|
238
|
+
const leading = (slots) => {
|
|
239
|
+
const empty = slots.indexOf(null);
|
|
240
|
+
return empty < 0 ? slots.length : empty;
|
|
241
|
+
};
|
|
242
|
+
const STATE_TAG = utf8('zerc20/storage/sync-state/v1');
|
|
243
|
+
/** `state`, encrypted under a key only `view` derives, for the app to store anywhere. */
|
|
244
|
+
export function exportState(view, state) {
|
|
245
|
+
checkState(view, state);
|
|
246
|
+
const json = JSON.stringify(state, (_, v) => typeof v === 'bigint' ? { $n: v.toString() } : v instanceof Uint8Array ? { $b: hex(v) } : v);
|
|
247
|
+
return seal(kdf(view.seed, 'sync-state'), concat(STATE_TAG, view.address), utf8(json));
|
|
248
|
+
}
|
|
249
|
+
/** The state `exportState` encrypted, or `null` if `bytes` is not one for `view` (start over from
|
|
250
|
+
* `emptyState` then). */
|
|
251
|
+
export function importState(view, bytes) {
|
|
252
|
+
const plain = open(kdf(view.seed, 'sync-state'), concat(STATE_TAG, view.address), bytes);
|
|
253
|
+
if (!plain)
|
|
254
|
+
return null;
|
|
255
|
+
return JSON.parse(new TextDecoder().decode(plain), (_, v) => {
|
|
256
|
+
if (typeof v === 'object' && v !== null && '$n' in v)
|
|
257
|
+
return BigInt(v.$n);
|
|
258
|
+
if (typeof v === 'object' && v !== null && '$b' in v)
|
|
259
|
+
return unhex(v.$b);
|
|
260
|
+
return v;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { DerivedPublicKey, TransportSecretKey } from '@dfinity/vetkeys';
|
|
2
|
+
import type { Principal } from '@icp-sdk/core/principal';
|
|
3
|
+
import type { Registration } from './candid.js';
|
|
4
|
+
import type { CanisterClient } from './canister.js';
|
|
5
|
+
/** EIP-191 `personal_sign` by the address's wallet: `r ‖ s ‖ v`, 65 bytes. */
|
|
6
|
+
export type Signer = (message: string) => Promise<Uint8Array>;
|
|
7
|
+
/** An address's view key: the seed everything owner-only derives from. */
|
|
8
|
+
export interface ViewKey {
|
|
9
|
+
address: Uint8Array;
|
|
10
|
+
seed: Uint8Array;
|
|
11
|
+
}
|
|
12
|
+
/** `address`'s view key, from the root seed of the address's zERC20 identity (in zERC20-v2, `keccak256` of a
|
|
13
|
+
* canonical `personal_sign` signature). */
|
|
14
|
+
export declare function viewKey(rootSeed: Uint8Array, address: Uint8Array): ViewKey;
|
|
15
|
+
/** The X25519 secret that opens announcements to `view`'s address inbox. */
|
|
16
|
+
export declare const notifySecret: (view: ViewKey) => Uint8Array;
|
|
17
|
+
/** The key payers encrypt announcements to, which names `view`'s registration. */
|
|
18
|
+
export declare const notifyKey: (view: ViewKey) => Uint8Array;
|
|
19
|
+
/** `view`'s address registered with `publicKey`, the zERC20 public key payments to it are to be owned by. The first
|
|
20
|
+
* check is a query, answered by one replica: only a missing or foreign registration is confirmed with an update call
|
|
21
|
+
* before registering (one wallet signature) or throwing. Throws if the address is registered with another root seed;
|
|
22
|
+
* `register` to take it over. */
|
|
23
|
+
export declare function registeredViewKey(client: CanisterClient, rootSeed: Uint8Array, signer: Signer, address: Uint8Array, publicKey: Uint8Array): Promise<ViewKey>;
|
|
24
|
+
/** Registers `view`'s address with `publicKey`, replacing whatever it registered before. */
|
|
25
|
+
export declare function register(client: CanisterClient, signer: Signer, view: ViewKey, publicKey: Uint8Array): Promise<void>;
|
|
26
|
+
/** The address that signed `message` with EIP-191 `personal_sign`, or `null`. */
|
|
27
|
+
export declare function eip191Signer(message: string, signature: Uint8Array): Uint8Array | null;
|
|
28
|
+
/** Whether `r` is what `address` signed: the canister checks this on write, and a payer checks it again so that a
|
|
29
|
+
* canister cannot point it at keys of its own. */
|
|
30
|
+
export declare const verifyRegistration: (canister: Principal, address: Uint8Array, r: Registration) => boolean;
|
|
31
|
+
/** Whom a payment to an address goes to: the zERC20 public key to own the note, and the inbox and key to announce a
|
|
32
|
+
* channel to. */
|
|
33
|
+
export interface Recipient {
|
|
34
|
+
address: Uint8Array;
|
|
35
|
+
publicKey: Uint8Array;
|
|
36
|
+
notifyKey: Uint8Array;
|
|
37
|
+
inbox: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
/** `address` as a recipient, checked against its owner's signature, or `null` when it never registered: pay it
|
|
40
|
+
* publicly instead, or ask for a receive code. An update call, so that no single replica can answer with a
|
|
41
|
+
* registration the address has replaced; a canister can withhold one, but not forge one. */
|
|
42
|
+
export declare function lookupRecipient(client: CanisterClient, address: Uint8Array): Promise<Recipient | null>;
|
|
43
|
+
/** For the auditor: the view key in `registration`'s escrow, from what `audit_escrow_key` returned (encrypted to
|
|
44
|
+
* `transportKey`). `null` if the key or the escrow does not open. */
|
|
45
|
+
export declare function openEscrow(encrypted: Uint8Array, transportKey: TransportSecretKey, escrowPublicKey: DerivedPublicKey, registration: Registration): ViewKey | null;
|
package/dist/viewKey.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// View keys and registration.
|
|
2
|
+
//
|
|
3
|
+
// An address's view key derives from the root seed of the address's zERC20 identity, so restoring a wallet needs
|
|
4
|
+
// nothing from the canister. Registering (one wallet signature) publishes what payers need: the address's zERC20
|
|
5
|
+
// public key and the key payers encrypt channel announcements to. It also escrows the view key to the auditor,
|
|
6
|
+
// whom vetKD alone can give the key to it. A later registration, from another root seed, replaces it.
|
|
7
|
+
import { EncryptedVetKey, IbeCiphertext, IbeIdentity, IbeSeed, } from '@dfinity/vetkeys';
|
|
8
|
+
import { x25519 } from '@noble/curves/ed25519.js';
|
|
9
|
+
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
10
|
+
import { keccak_256 } from '@noble/hashes/sha3.js';
|
|
11
|
+
import { compare, concat, equal, kdf, nowNs, utf8 } from './crypto.js';
|
|
12
|
+
import { FR_BE, addressInbox } from './formats.js';
|
|
13
|
+
import { MAX_TTL_NS, registerMessage, vetkdInput } from './wire.js';
|
|
14
|
+
/** `address`'s view key, from the root seed of the address's zERC20 identity (in zERC20-v2, `keccak256` of a
|
|
15
|
+
* canonical `personal_sign` signature). */
|
|
16
|
+
export function viewKey(rootSeed, address) {
|
|
17
|
+
if (rootSeed.length !== 32)
|
|
18
|
+
throw new Error('the root seed must be 32 bytes');
|
|
19
|
+
return { address, seed: kdf(rootSeed, 'view', [address]) };
|
|
20
|
+
}
|
|
21
|
+
/** The X25519 secret that opens announcements to `view`'s address inbox. */
|
|
22
|
+
export const notifySecret = (view) => kdf(view.seed, 'notify');
|
|
23
|
+
/** The key payers encrypt announcements to, which names `view`'s registration. */
|
|
24
|
+
export const notifyKey = (view) => x25519.getPublicKey(notifySecret(view));
|
|
25
|
+
/** `view`'s address registered with `publicKey`, the zERC20 public key payments to it are to be owned by. The first
|
|
26
|
+
* check is a query, answered by one replica: only a missing or foreign registration is confirmed with an update call
|
|
27
|
+
* before registering (one wallet signature) or throwing. Throws if the address is registered with another root seed;
|
|
28
|
+
* `register` to take it over. */
|
|
29
|
+
export async function registeredViewKey(client, rootSeed, signer, address, publicKey) {
|
|
30
|
+
const view = viewKey(rootSeed, address);
|
|
31
|
+
const ours = (r) => r && equal(r.notify_key, notifyKey(view)) && equal(r.public_key, publicKey);
|
|
32
|
+
if (ours(await client.registration(address)))
|
|
33
|
+
return view;
|
|
34
|
+
const [r] = await client.replicated.registration(address, []);
|
|
35
|
+
if (r && !equal(r.notify_key, notifyKey(view))) {
|
|
36
|
+
throw new Error('the address is registered with another root seed: register to take it over');
|
|
37
|
+
}
|
|
38
|
+
if (!ours(r ?? null))
|
|
39
|
+
await register(client, signer, view, publicKey);
|
|
40
|
+
return view;
|
|
41
|
+
}
|
|
42
|
+
/** Registers `view`'s address with `publicKey`, replacing whatever it registered before. */
|
|
43
|
+
export async function register(client, signer, view, publicKey) {
|
|
44
|
+
if (publicKey.length !== 32)
|
|
45
|
+
throw new Error('a public key is 32 bytes');
|
|
46
|
+
const notify = notifyKey(view);
|
|
47
|
+
const identity = IbeIdentity.fromBytes(vetkdInput(view.address, notify));
|
|
48
|
+
const r = {
|
|
49
|
+
address: view.address,
|
|
50
|
+
public_key: publicKey,
|
|
51
|
+
notify_key: notify,
|
|
52
|
+
escrow: IbeCiphertext.encrypt(await client.escrowPublicKey(), identity, view.seed, IbeSeed.random()).serialize(),
|
|
53
|
+
expiry_ns: nowNs() + MAX_TTL_NS / 2n,
|
|
54
|
+
signature: new Uint8Array(),
|
|
55
|
+
};
|
|
56
|
+
r.signature = await signer(registerMessage(client.canisterId, r));
|
|
57
|
+
await client.register(r);
|
|
58
|
+
}
|
|
59
|
+
/** The address that signed `message` with EIP-191 `personal_sign`, or `null`. */
|
|
60
|
+
export function eip191Signer(message, signature) {
|
|
61
|
+
if (signature.length !== 65)
|
|
62
|
+
return null;
|
|
63
|
+
const v = signature[64] >= 27 ? signature[64] - 27 : signature[64];
|
|
64
|
+
if (v > 1)
|
|
65
|
+
return null;
|
|
66
|
+
const msg = utf8(message);
|
|
67
|
+
const digest = keccak_256(concat(utf8(`\x19Ethereum Signed Message:\n${msg.length}`), msg));
|
|
68
|
+
try {
|
|
69
|
+
const sig = secp256k1.Signature.fromBytes(concat(Uint8Array.of(v), signature.subarray(0, 64)), 'recovered');
|
|
70
|
+
if (sig.hasHighS())
|
|
71
|
+
return null;
|
|
72
|
+
return keccak_256(sig.recoverPublicKey(digest).toBytes(false).subarray(1)).slice(12);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** Whether `r` is what `address` signed: the canister checks this on write, and a payer checks it again so that a
|
|
79
|
+
* canister cannot point it at keys of its own. */
|
|
80
|
+
export const verifyRegistration = (canister, address, r) => equal(r.address, address) &&
|
|
81
|
+
equal(eip191Signer(registerMessage(canister, r), r.signature) ?? new Uint8Array(), address);
|
|
82
|
+
/** `address` as a recipient, checked against its owner's signature, or `null` when it never registered: pay it
|
|
83
|
+
* publicly instead, or ask for a receive code. An update call, so that no single replica can answer with a
|
|
84
|
+
* registration the address has replaced; a canister can withhold one, but not forge one. */
|
|
85
|
+
export async function lookupRecipient(client, address) {
|
|
86
|
+
const [r] = await client.replicated.registration(address, []);
|
|
87
|
+
if (!r || !verifyRegistration(client.canisterId, address, r) || compare(r.public_key, FR_BE) >= 0)
|
|
88
|
+
return null;
|
|
89
|
+
return { address, publicKey: r.public_key, notifyKey: r.notify_key, inbox: addressInbox(address, r.notify_key) };
|
|
90
|
+
}
|
|
91
|
+
/** For the auditor: the view key in `registration`'s escrow, from what `audit_escrow_key` returned (encrypted to
|
|
92
|
+
* `transportKey`). `null` if the key or the escrow does not open. */
|
|
93
|
+
export function openEscrow(encrypted, transportKey, escrowPublicKey, registration) {
|
|
94
|
+
const { address, notify_key } = registration;
|
|
95
|
+
try {
|
|
96
|
+
const vetKey = EncryptedVetKey.deserialize(encrypted).decryptAndVerify(transportKey, escrowPublicKey, vetkdInput(address, notify_key));
|
|
97
|
+
const seed = IbeCiphertext.deserialize(registration.escrow).decrypt(vetKey);
|
|
98
|
+
return seed.length === 32 ? { address, seed } : null;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=viewKey.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewKey.js","sourceRoot":"","sources":["../src/viewKey.ts"],"names":[],"mappings":"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"}
|
package/dist/wire.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Principal } from '@icp-sdk/core/principal';
|
|
2
|
+
import type { Action, Registration } from './candid.js';
|
|
3
|
+
/** Sized for a five-year-old budget Android phone (the canister's `lib.rs` says how). */
|
|
4
|
+
export declare const POW_BITS = 14;
|
|
5
|
+
/** Address inboxes are public, so opening a channel costs more work than a plain write. */
|
|
6
|
+
export declare const OPEN_POW_BITS = 17;
|
|
7
|
+
export declare const REGISTER_POW_BITS = 18;
|
|
8
|
+
/** Every blob is exactly this long, so its length tells nothing of what it holds. */
|
|
9
|
+
export declare const BLOB_LEN = 256;
|
|
10
|
+
export declare const MAX_READ = 256;
|
|
11
|
+
/** Lane slot ids per `get_log` call. */
|
|
12
|
+
export declare const MAX_LOG_READ = 8192;
|
|
13
|
+
/** Longest a signed request stays valid. */
|
|
14
|
+
export declare const MAX_TTL_NS = 600000000000n;
|
|
15
|
+
/** The vetKD input, which is also the IBE identity, of the key to the escrow of `address`'s registration with
|
|
16
|
+
* `notifyKey`. */
|
|
17
|
+
export declare const vetkdInput: (address: Uint8Array, notifyKey: Uint8Array) => Uint8Array;
|
|
18
|
+
/** What an address owner signs (EIP-191) to register. */
|
|
19
|
+
export declare function registerMessage(canister: Principal, r: Registration): string;
|
|
20
|
+
/** What the auditor signs (EIP-191) to receive the key to the escrow of `target`'s registration with `notifyKey`. */
|
|
21
|
+
export declare function auditMessage(canister: Principal, target: Uint8Array, notifyKey: Uint8Array, transportPublicKey: Uint8Array, expiryNs: bigint): string;
|
|
22
|
+
export declare function registerDigest(r: Registration): Uint8Array;
|
|
23
|
+
export declare function actionsDigest(actions: Action[]): Uint8Array;
|
|
24
|
+
export declare const requiredPowBits: (actions: Action[]) => number;
|
|
25
|
+
export declare function powOk(digest: Uint8Array, nonce: bigint, bits: number): boolean;
|
|
26
|
+
/** The first nonce whose proof of work reaches `bits`, searched in a Web Worker so a page stays
|
|
27
|
+
* responsive. Where there is none (Node), or it does not start (a bundler that left out
|
|
28
|
+
* powWorker.js), on this thread, yielding every ~100 ms. */
|
|
29
|
+
export declare function solvePow(digest: Uint8Array, bits: number): Promise<bigint>;
|
|
30
|
+
/** The first nonce whose proof of work reaches `bits`. On a page's thread, yields to the event loop
|
|
31
|
+
* every `yieldMs`, and no more often: each yield is a timer, which costs 4 ms in a visible tab and a
|
|
32
|
+
* whole second in a hidden one. */
|
|
33
|
+
export declare function findNonce(digest: Uint8Array, bits: number, yieldMs: number): Promise<bigint>;
|
package/dist/wire.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Bytes the canister checks: signed messages and proof-of-work digests, and the vetKD input. They must match
|
|
2
|
+
// canister/src/registry.rs, audit.rs and store.rs byte for byte; test/wire.test.ts pins the same fixtures as the
|
|
3
|
+
// canister's `wire_fixtures` test.
|
|
4
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
5
|
+
import { concat, hex, lengthPrefixed, taggedHash, u64be, utf8 } from './crypto.js';
|
|
6
|
+
/** Sized for a five-year-old budget Android phone (the canister's `lib.rs` says how). */
|
|
7
|
+
export const POW_BITS = 14;
|
|
8
|
+
/** Address inboxes are public, so opening a channel costs more work than a plain write. */
|
|
9
|
+
export const OPEN_POW_BITS = 17;
|
|
10
|
+
export const REGISTER_POW_BITS = 18;
|
|
11
|
+
/** Every blob is exactly this long, so its length tells nothing of what it holds. */
|
|
12
|
+
export const BLOB_LEN = 256;
|
|
13
|
+
export const MAX_READ = 256;
|
|
14
|
+
/** Lane slot ids per `get_log` call. */
|
|
15
|
+
export const MAX_LOG_READ = 8192;
|
|
16
|
+
/** Longest a signed request stays valid. */
|
|
17
|
+
export const MAX_TTL_NS = 600000000000n;
|
|
18
|
+
/** The canister's `Kind` numbers (Rust's, not the .did's alphabetical order). */
|
|
19
|
+
const KIND_CODE = { Outgoing: 0, Lane: 1 };
|
|
20
|
+
/** Tags a registration's proof-of-work digest and the text its owner signs. */
|
|
21
|
+
const REGISTRATION_TAG = 'zerc20/storage/registration/v1';
|
|
22
|
+
/** The vetKD input, which is also the IBE identity, of the key to the escrow of `address`'s registration with
|
|
23
|
+
* `notifyKey`. */
|
|
24
|
+
export const vetkdInput = (address, notifyKey) => concat(address, notifyKey);
|
|
25
|
+
/** What an address owner signs (EIP-191) to register. */
|
|
26
|
+
export function registerMessage(canister, r) {
|
|
27
|
+
return (`zERC20 storage registration\ncanister: ${canister.toText()}\naddress: 0x${hex(r.address)}\n` +
|
|
28
|
+
`public_key: 0x${hex(r.public_key)}\nnotify_key: 0x${hex(r.notify_key)}\n` +
|
|
29
|
+
`escrow_sha256: 0x${hex(sha256(r.escrow))}\nexpiry_ns: ${r.expiry_ns}\ntag: ${REGISTRATION_TAG}`);
|
|
30
|
+
}
|
|
31
|
+
/** What the auditor signs (EIP-191) to receive the key to the escrow of `target`'s registration with `notifyKey`. */
|
|
32
|
+
export function auditMessage(canister, target, notifyKey, transportPublicKey, expiryNs) {
|
|
33
|
+
return (`zERC20 storage audit request\ncanister: ${canister.toText()}\ntarget: 0x${hex(target)}\n` +
|
|
34
|
+
`notify_key: 0x${hex(notifyKey)}\ntransport_public_key: 0x${hex(transportPublicKey)}\nexpiry_ns: ${expiryNs}\n` +
|
|
35
|
+
'tag: zerc20/storage/audit/v1');
|
|
36
|
+
}
|
|
37
|
+
export function registerDigest(r) {
|
|
38
|
+
return taggedHash(REGISTRATION_TAG, [r.address, r.public_key, r.notify_key, r.escrow, u64be(r.expiry_ns)]);
|
|
39
|
+
}
|
|
40
|
+
export function actionsDigest(actions) {
|
|
41
|
+
const parts = actions.flatMap((action) => {
|
|
42
|
+
if ('OpenChannel' in action) {
|
|
43
|
+
const { inbox, enc_announcement, outgoing_id, enc_outgoing } = action.OpenChannel;
|
|
44
|
+
return [Uint8Array.of(0), lengthPrefixed([inbox, enc_announcement, outgoing_id, enc_outgoing])];
|
|
45
|
+
}
|
|
46
|
+
const { kind, id, data } = action.Put;
|
|
47
|
+
const code = KIND_CODE[Object.keys(kind)[0]];
|
|
48
|
+
return [Uint8Array.of(1, code), lengthPrefixed([id, data])];
|
|
49
|
+
});
|
|
50
|
+
return sha256(concat(...parts));
|
|
51
|
+
}
|
|
52
|
+
export const requiredPowBits = (actions) => actions.some((action) => 'OpenChannel' in action) ? OPEN_POW_BITS : POW_BITS;
|
|
53
|
+
const POW_TAG = utf8('zerc20/storage/pow/v1');
|
|
54
|
+
function leadingZeroBits(bytes) {
|
|
55
|
+
let zeros = 0;
|
|
56
|
+
for (const byte of bytes) {
|
|
57
|
+
if (byte !== 0)
|
|
58
|
+
return zeros + Math.clz32(byte) - 24;
|
|
59
|
+
zeros += 8;
|
|
60
|
+
}
|
|
61
|
+
return zeros;
|
|
62
|
+
}
|
|
63
|
+
export function powOk(digest, nonce, bits) {
|
|
64
|
+
return leadingZeroBits(sha256(concat(POW_TAG, digest, u64be(nonce)))) >= bits;
|
|
65
|
+
}
|
|
66
|
+
/** The first nonce whose proof of work reaches `bits`, searched in a Web Worker so a page stays
|
|
67
|
+
* responsive. Where there is none (Node), or it does not start (a bundler that left out
|
|
68
|
+
* powWorker.js), on this thread, yielding every ~100 ms. */
|
|
69
|
+
export async function solvePow(digest, bits) {
|
|
70
|
+
if (typeof Worker !== 'undefined') {
|
|
71
|
+
try {
|
|
72
|
+
return await inWorker(digest, bits);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// searched here instead
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return findNonce(digest, bits, 100);
|
|
79
|
+
}
|
|
80
|
+
async function inWorker(digest, bits) {
|
|
81
|
+
// this exact form is what bundlers (Vite, webpack) look for to emit the worker
|
|
82
|
+
const worker = new Worker(new URL('./powWorker.js', import.meta.url), { type: 'module' });
|
|
83
|
+
try {
|
|
84
|
+
return await new Promise((resolve, reject) => {
|
|
85
|
+
worker.onmessage = ({ data }) => resolve(data);
|
|
86
|
+
worker.onerror = reject;
|
|
87
|
+
worker.postMessage({ digest, bits });
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
worker.terminate();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** The first nonce whose proof of work reaches `bits`. On a page's thread, yields to the event loop
|
|
95
|
+
* every `yieldMs`, and no more often: each yield is a timer, which costs 4 ms in a visible tab and a
|
|
96
|
+
* whole second in a hidden one. */
|
|
97
|
+
export async function findNonce(digest, bits, yieldMs) {
|
|
98
|
+
const buffer = concat(POW_TAG, digest, new Uint8Array(8));
|
|
99
|
+
const nonce = new DataView(buffer.buffer, POW_TAG.length + digest.length, 8);
|
|
100
|
+
let since = Date.now();
|
|
101
|
+
for (let n = 0;; n++) {
|
|
102
|
+
nonce.setUint32(0, Math.floor(n / 2 ** 32), false);
|
|
103
|
+
nonce.setUint32(4, n >>> 0, false);
|
|
104
|
+
if (leadingZeroBits(sha256(buffer)) >= bits)
|
|
105
|
+
return BigInt(n);
|
|
106
|
+
if (n % 4096 === 4095 && Date.now() - since >= yieldMs) {
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
108
|
+
since = Date.now();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=wire.js.map
|
package/dist/wire.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"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"}
|