@qntx/wallet-worker 0.1.0 → 0.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/README.md +27 -8
- package/dist/browser-gx857d3g.js +199 -0
- package/dist/browser.mjs +55 -25
- package/dist/browser.mjs.map +1 -1
- package/dist/index-cgstn3zw.js +26 -0
- package/dist/kernel.manifest.json +108 -0
- package/dist/kernel.node.js +7039 -8447
- package/dist/kernel.worker.js +7208 -8480
- package/dist/node-30nrftxx.js +23 -0
- package/dist/node-7sztn879.js +4446 -0
- package/dist/node-nhrchcf5.js +3979 -0
- package/dist/node.d.mts +3 -1
- package/dist/node.mjs +96 -24
- package/dist/node.mjs.map +1 -1
- package/dist/signer-tgawen1v.js +6459 -0
- package/dist/spark.wasm +0 -0
- package/dist/worker-30nrftxx.js +23 -0
- package/dist/worker-7sztn879.js +4446 -0
- package/dist/worker-nhrchcf5.js +3979 -0
- package/package.json +16 -11
package/dist/node.d.mts
CHANGED
|
@@ -2,7 +2,9 @@ import { AutoLockPolicy, PasskeyConfig, Wallet } from "@qntx/wallet";
|
|
|
2
2
|
import { PasskeyCeremony } from "@qntx/wallet/host";
|
|
3
3
|
import { Worker } from "node:worker_threads";
|
|
4
4
|
//#region src/node/software-passkey.d.ts
|
|
5
|
-
export declare function createSoftwarePasskey(seed: Uint8Array
|
|
5
|
+
export declare function createSoftwarePasskey(seed: Uint8Array, options?: {
|
|
6
|
+
readonly synced?: boolean;
|
|
7
|
+
}): PasskeyCeremony;
|
|
6
8
|
//#endregion
|
|
7
9
|
//#region src/node/index.d.ts
|
|
8
10
|
export type NodeWalletOptions = {
|
package/dist/node.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WalletError } from "@qntx/wallet";
|
|
2
|
-
import { connectKernel } from "@qntx/wallet/host";
|
|
2
|
+
import { PASSKEY_PURPOSE_VAULT, PASSKEY_PURPOSE_WALLET, connectKernel } from "@qntx/wallet/host";
|
|
3
3
|
import { MessageChannel, Worker } from "node:worker_threads";
|
|
4
4
|
//#region ../wallet-kernel/src/errors.ts
|
|
5
5
|
/**
|
|
@@ -806,10 +806,19 @@ function cloneImage(image) {
|
|
|
806
806
|
factors: image.factors.map(cloneFactor)
|
|
807
807
|
};
|
|
808
808
|
}
|
|
809
|
+
function conflict(message) {
|
|
810
|
+
return new KernelError("conflict", `vault store: ${message}`);
|
|
811
|
+
}
|
|
812
|
+
function requireMeta(meta) {
|
|
813
|
+
if (meta === void 0) throw conflict("vault is not initialized");
|
|
814
|
+
return meta;
|
|
815
|
+
}
|
|
809
816
|
/**
|
|
810
817
|
* In-memory RecordStore for the Node host and kernel tests. Loads and commits deep-copy every
|
|
811
818
|
* Uint8Array, so callers can never mutate stored state. `commit` applies ops to a clone and swaps
|
|
812
|
-
* atomically: a failing `expect-
|
|
819
|
+
* atomically: a failing precondition (`init`, `expect-revision`, a counter op on an empty store)
|
|
820
|
+
* leaves the store untouched. Content ops bump `meta.revision` once per commit; an `init` commit
|
|
821
|
+
* writes its meta verbatim (revision stays as the initializer set it).
|
|
813
822
|
*/
|
|
814
823
|
function createMemoryStore() {
|
|
815
824
|
let image = {
|
|
@@ -822,30 +831,65 @@ function createMemoryStore() {
|
|
|
822
831
|
let { meta } = image;
|
|
823
832
|
let { records } = image;
|
|
824
833
|
let { factors } = image;
|
|
834
|
+
let bump = false;
|
|
835
|
+
let initialized = false;
|
|
825
836
|
for (const op of ops) switch (op.op) {
|
|
826
|
-
case "
|
|
827
|
-
if (meta !== void 0 || records.length > 0 || factors.length > 0) throw
|
|
828
|
-
break;
|
|
829
|
-
case "put-meta":
|
|
837
|
+
case "init":
|
|
838
|
+
if (meta !== void 0 || records.length > 0 || factors.length > 0) throw conflict("vault already initialized");
|
|
830
839
|
meta = cloneMeta(op.meta);
|
|
840
|
+
initialized = true;
|
|
841
|
+
break;
|
|
842
|
+
case "expect-revision":
|
|
843
|
+
if (meta === void 0 || meta.revision !== op.revision) throw conflict("vault changed underneath");
|
|
844
|
+
break;
|
|
845
|
+
case "set-key-id":
|
|
846
|
+
meta = {
|
|
847
|
+
...requireMeta(meta),
|
|
848
|
+
keyId: op.keyId
|
|
849
|
+
};
|
|
850
|
+
bump = true;
|
|
851
|
+
break;
|
|
852
|
+
case "record-failure": {
|
|
853
|
+
const current = requireMeta(meta);
|
|
854
|
+
meta = {
|
|
855
|
+
...current,
|
|
856
|
+
failedAttempts: current.failedAttempts + 1,
|
|
857
|
+
lastFailureAt: op.at
|
|
858
|
+
};
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
case "reset-failures":
|
|
862
|
+
meta = {
|
|
863
|
+
...requireMeta(meta),
|
|
864
|
+
failedAttempts: 0,
|
|
865
|
+
lastFailureAt: 0
|
|
866
|
+
};
|
|
831
867
|
break;
|
|
832
868
|
case "put-record":
|
|
833
869
|
records = [...records.filter((record) => !(record.kind === op.record.kind && record.id === op.record.id)), cloneRecord(op.record)];
|
|
870
|
+
bump = true;
|
|
834
871
|
break;
|
|
835
872
|
case "delete-record":
|
|
836
873
|
records = records.filter((record) => !(record.kind === op.kind && record.id === op.id));
|
|
874
|
+
bump = true;
|
|
837
875
|
break;
|
|
838
876
|
case "put-factor":
|
|
839
877
|
factors = [...factors.filter((factor) => factor.id !== op.factor.id), cloneFactor(op.factor)];
|
|
878
|
+
bump = true;
|
|
840
879
|
break;
|
|
841
880
|
case "delete-factor":
|
|
842
881
|
factors = factors.filter((factor) => factor.id !== op.id);
|
|
882
|
+
bump = true;
|
|
843
883
|
break;
|
|
844
884
|
case "clear":
|
|
845
885
|
meta = void 0;
|
|
846
886
|
records = [];
|
|
847
887
|
factors = [];
|
|
848
888
|
}
|
|
889
|
+
if (bump && !initialized && meta !== void 0) meta = {
|
|
890
|
+
...meta,
|
|
891
|
+
revision: meta.revision + 1
|
|
892
|
+
};
|
|
849
893
|
return {
|
|
850
894
|
meta,
|
|
851
895
|
records,
|
|
@@ -890,13 +934,25 @@ function serveStore(store, port) {
|
|
|
890
934
|
const post = (reply) => {
|
|
891
935
|
port.postMessage(reply);
|
|
892
936
|
};
|
|
937
|
+
const failed = (id, error) => {
|
|
938
|
+
const code = error instanceof KernelError ? error.code : "unavailable";
|
|
939
|
+
post({
|
|
940
|
+
t: "failed",
|
|
941
|
+
id,
|
|
942
|
+
code
|
|
943
|
+
});
|
|
944
|
+
};
|
|
893
945
|
const answer = async (request) => {
|
|
894
946
|
if (request.t === "load") {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
947
|
+
try {
|
|
948
|
+
post({
|
|
949
|
+
t: "loaded",
|
|
950
|
+
id: request.id,
|
|
951
|
+
image: encodeImage(await store.load())
|
|
952
|
+
});
|
|
953
|
+
} catch (error) {
|
|
954
|
+
failed(request.id, error);
|
|
955
|
+
}
|
|
900
956
|
return;
|
|
901
957
|
}
|
|
902
958
|
ownCommits++;
|
|
@@ -904,7 +960,8 @@ function serveStore(store, port) {
|
|
|
904
960
|
await store.commit(request.ops);
|
|
905
961
|
} catch (error) {
|
|
906
962
|
ownCommits--;
|
|
907
|
-
|
|
963
|
+
failed(request.id, error);
|
|
964
|
+
return;
|
|
908
965
|
}
|
|
909
966
|
post({
|
|
910
967
|
t: "committed",
|
|
@@ -943,8 +1000,11 @@ function serveStore(store, port) {
|
|
|
943
1000
|
*
|
|
944
1001
|
* CredentialId = SHA-256(seed ‖ "credential" ‖ u32be(n)) (n increments per create) PRF(salt) =
|
|
945
1002
|
* HMAC-SHA256(HMAC-SHA256(seed, credentialId), SHA-256(UTF-8("WebAuthn PRF") ‖ 0x00 ‖ salt))
|
|
1003
|
+
* UserHandle = purpose byte ‖ SHA-256(seed ‖ "user-handle" ‖ credentialId)[0:15] — the same
|
|
1004
|
+
* 16-byte, purpose-prefixed shape the browser create path puts in `user.id`.
|
|
946
1005
|
*/
|
|
947
|
-
function createSoftwarePasskey(seed) {
|
|
1006
|
+
function createSoftwarePasskey(seed, options) {
|
|
1007
|
+
const synced = options?.synced ?? true;
|
|
948
1008
|
let counter = 0;
|
|
949
1009
|
let lastCreated;
|
|
950
1010
|
const known = /* @__PURE__ */ new Map();
|
|
@@ -953,25 +1013,35 @@ function createSoftwarePasskey(seed) {
|
|
|
953
1013
|
const message = sha256(concatBytes(utf8ToBytes("WebAuthn PRF"), new Uint8Array([0]), salt));
|
|
954
1014
|
return hmac(sha256, key, message);
|
|
955
1015
|
};
|
|
956
|
-
const result = (credentialId, salts) => ({
|
|
1016
|
+
const result = (credentialId, salts, userHandle) => ({
|
|
957
1017
|
credentialId: new Uint8Array(credentialId),
|
|
958
1018
|
first: prf(credentialId, salts.first),
|
|
959
|
-
...salts.second === void 0 ? {} : { second: prf(credentialId, salts.second) }
|
|
1019
|
+
...salts.second === void 0 ? {} : { second: prf(credentialId, salts.second) },
|
|
1020
|
+
...userHandle === void 0 ? {} : { userHandle: new Uint8Array(userHandle) },
|
|
1021
|
+
synced
|
|
960
1022
|
});
|
|
961
1023
|
return {
|
|
962
|
-
create: async ({ salts }) => {
|
|
1024
|
+
create: async ({ purpose, salts }) => {
|
|
963
1025
|
const counterBytes = /* @__PURE__ */ new Uint8Array(4);
|
|
964
1026
|
new DataView(counterBytes.buffer).setUint32(0, counter);
|
|
965
1027
|
counter++;
|
|
966
1028
|
const credentialId = sha256(concatBytes(seed, utf8ToBytes("credential"), counterBytes));
|
|
967
|
-
|
|
1029
|
+
const purposeByte = purpose === "wallet" ? PASSKEY_PURPOSE_WALLET : PASSKEY_PURPOSE_VAULT;
|
|
1030
|
+
const userHandle = /* @__PURE__ */ new Uint8Array(16);
|
|
1031
|
+
userHandle[0] = purposeByte;
|
|
1032
|
+
userHandle.set(sha256(concatBytes(seed, utf8ToBytes("user-handle"), credentialId)).subarray(0, 15), 1);
|
|
1033
|
+
known.set(bytesToHex(credentialId), {
|
|
1034
|
+
credentialId,
|
|
1035
|
+
userHandle
|
|
1036
|
+
});
|
|
968
1037
|
lastCreated = credentialId;
|
|
969
1038
|
return result(credentialId, salts);
|
|
970
1039
|
},
|
|
971
1040
|
assert: async ({ credentialIds, salts }) => {
|
|
972
1041
|
const credentialId = credentialIds === void 0 ? lastCreated : credentialIds.find((id) => known.has(bytesToHex(id)));
|
|
973
1042
|
if (credentialId === void 0) throw new WalletError("factor-unavailable", credentialIds === void 0 ? "no software credential exists yet" : "unknown software credential");
|
|
974
|
-
|
|
1043
|
+
const entry = known.get(bytesToHex(credentialId));
|
|
1044
|
+
return result(credentialId, salts, entry?.userHandle);
|
|
975
1045
|
}
|
|
976
1046
|
};
|
|
977
1047
|
}
|
|
@@ -983,14 +1053,10 @@ function createSoftwarePasskey(seed) {
|
|
|
983
1053
|
* in-memory store, served to each worker over a MessageChannel. That is what lets a lock destroy
|
|
984
1054
|
* the whole worker without losing the vault.
|
|
985
1055
|
*/
|
|
986
|
-
const MAX_SESSION_MS = 432e5;
|
|
987
1056
|
async function createNodeWallet(options) {
|
|
988
1057
|
const { autoLock } = options;
|
|
989
1058
|
if (autoLock.platform !== "web") throw new WalletError("invalid-input", "createNodeWallet accepts only the web autoLock policy");
|
|
990
|
-
const workerData = {
|
|
991
|
-
idleMs: autoLock.idleMinutes * 6e4,
|
|
992
|
-
maxSessionMs: MAX_SESSION_MS
|
|
993
|
-
};
|
|
1059
|
+
const workerData = { idleMinutes: autoLock.idleMinutes };
|
|
994
1060
|
const store = createMemoryStore();
|
|
995
1061
|
const kernelUrl = options.kernelUrl ?? new URL("kernel.node.js", import.meta.url);
|
|
996
1062
|
const spawn = () => {
|
|
@@ -1035,7 +1101,13 @@ async function createNodeWallet(options) {
|
|
|
1035
1101
|
return connectKernel({
|
|
1036
1102
|
spawn,
|
|
1037
1103
|
passkey: options.passkey,
|
|
1038
|
-
ceremony: options.authenticator
|
|
1104
|
+
ceremony: options.authenticator,
|
|
1105
|
+
setTimer: (delayMs, fn) => {
|
|
1106
|
+
const timer = setTimeout(fn, delayMs);
|
|
1107
|
+
return () => {
|
|
1108
|
+
clearTimeout(timer);
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1039
1111
|
});
|
|
1040
1112
|
}
|
|
1041
1113
|
//#endregion
|