@volr/sdk-core 0.1.65 → 0.1.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +87 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -1
- package/dist/index.d.ts +28 -1
- package/dist/index.js +87 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -79,6 +79,15 @@ function getRandomBytes(len) {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// src/core/crypto/aes-gcm.ts
|
|
82
|
+
function generateNonce() {
|
|
83
|
+
const nonce = new Uint8Array(12);
|
|
84
|
+
const timestamp = Math.floor(Date.now() / 1e3);
|
|
85
|
+
const view = new DataView(nonce.buffer);
|
|
86
|
+
view.setUint32(0, timestamp, false);
|
|
87
|
+
const random = getRandomBytes(8);
|
|
88
|
+
nonce.set(random, 4);
|
|
89
|
+
return nonce;
|
|
90
|
+
}
|
|
82
91
|
function getSubtleCrypto() {
|
|
83
92
|
if (typeof crypto !== "undefined" && crypto.subtle) {
|
|
84
93
|
return crypto.subtle;
|
|
@@ -99,7 +108,7 @@ async function aesGcmEncrypt(plain, params) {
|
|
|
99
108
|
if (key.length !== 32) {
|
|
100
109
|
throw new Error(`${ERR_INVALID_PARAM}: key must be 32 bytes, got ${key.length}`);
|
|
101
110
|
}
|
|
102
|
-
const nonce = providedNonce ||
|
|
111
|
+
const nonce = providedNonce || generateNonce();
|
|
103
112
|
if (nonce.length !== 12) {
|
|
104
113
|
throw new Error(`${ERR_NONCE_SIZE}: nonce must be 12 bytes, got ${nonce.length}`);
|
|
105
114
|
}
|
|
@@ -1056,62 +1065,79 @@ async function getAuthNonce(client, from, mode) {
|
|
|
1056
1065
|
}
|
|
1057
1066
|
function createPasskeyProvider(passkey, options) {
|
|
1058
1067
|
let unwrappedKeypair = null;
|
|
1059
|
-
const
|
|
1060
|
-
|
|
1061
|
-
|
|
1068
|
+
const sessionTtlMs = options.sessionTtlMs ?? 0;
|
|
1069
|
+
let sessionExpiresAt = null;
|
|
1070
|
+
let lockTimer = null;
|
|
1071
|
+
const isSessionExpired = () => {
|
|
1072
|
+
if (sessionTtlMs === 0) return true;
|
|
1073
|
+
if (!sessionExpiresAt) return true;
|
|
1074
|
+
return Date.now() >= sessionExpiresAt;
|
|
1075
|
+
};
|
|
1076
|
+
const resetSessionTimer = () => {
|
|
1077
|
+
if (sessionTtlMs <= 0) return;
|
|
1078
|
+
if (lockTimer) {
|
|
1079
|
+
clearTimeout(lockTimer);
|
|
1080
|
+
lockTimer = null;
|
|
1081
|
+
}
|
|
1082
|
+
sessionExpiresAt = Date.now() + sessionTtlMs;
|
|
1083
|
+
lockTimer = setTimeout(async () => {
|
|
1084
|
+
console.log("[PasskeyProvider] Session expired, auto-locking");
|
|
1085
|
+
await lock();
|
|
1086
|
+
}, sessionTtlMs);
|
|
1087
|
+
};
|
|
1088
|
+
const lock = async () => {
|
|
1089
|
+
if (lockTimer) {
|
|
1090
|
+
clearTimeout(lockTimer);
|
|
1091
|
+
lockTimer = null;
|
|
1092
|
+
}
|
|
1093
|
+
sessionExpiresAt = null;
|
|
1094
|
+
if (unwrappedKeypair) {
|
|
1095
|
+
console.log("[PasskeyProvider] lock(): zeroizing sensitive data from memory");
|
|
1062
1096
|
zeroize(unwrappedKeypair.privateKey);
|
|
1063
1097
|
zeroize(unwrappedKeypair.publicKey);
|
|
1064
1098
|
unwrappedKeypair = null;
|
|
1065
1099
|
}
|
|
1066
|
-
|
|
1100
|
+
};
|
|
1101
|
+
const ensureSession = async (opts) => {
|
|
1102
|
+
if (opts?.force && unwrappedKeypair) {
|
|
1103
|
+
console.log("[PasskeyProvider] force=true: zeroizing existing session");
|
|
1104
|
+
await lock();
|
|
1105
|
+
}
|
|
1106
|
+
if (unwrappedKeypair && !isSessionExpired()) {
|
|
1107
|
+
resetSessionTimer();
|
|
1067
1108
|
return;
|
|
1068
1109
|
}
|
|
1069
|
-
if (
|
|
1070
|
-
console.log("[PasskeyProvider]
|
|
1071
|
-
|
|
1072
|
-
const { prfOutput } = await passkey.authenticate({
|
|
1073
|
-
salt: prfSalt,
|
|
1074
|
-
credentialId: options.prfInput.credentialId
|
|
1075
|
-
});
|
|
1076
|
-
const wrapKey = prfOutput;
|
|
1077
|
-
const aad = options.aad || new TextEncoder().encode("volr/master-seed/v1");
|
|
1078
|
-
const masterSeed = await unsealMasterSeed(
|
|
1079
|
-
options.encryptedBlob.cipher,
|
|
1080
|
-
wrapKey,
|
|
1081
|
-
aad,
|
|
1082
|
-
options.encryptedBlob.nonce
|
|
1083
|
-
);
|
|
1084
|
-
const keypair = deriveEvmKey({ masterSeed });
|
|
1085
|
-
unwrappedKeypair = {
|
|
1086
|
-
privateKey: keypair.privateKey,
|
|
1087
|
-
publicKey: keypair.publicKey,
|
|
1088
|
-
address: keypair.address
|
|
1089
|
-
};
|
|
1090
|
-
zeroize(masterSeed);
|
|
1091
|
-
zeroize(wrapKey);
|
|
1092
|
-
zeroize(prfSalt);
|
|
1093
|
-
} else {
|
|
1094
|
-
console.warn("[PasskeyProvider] Non-interactive mode: using deterministic wrap key (insecure for TTL=0)");
|
|
1095
|
-
const wrapKey = deriveWrapKey(options.prfInput);
|
|
1096
|
-
const aad = options.aad || new TextEncoder().encode("volr/master-seed/v1");
|
|
1097
|
-
const masterSeed = await unsealMasterSeed(
|
|
1098
|
-
options.encryptedBlob.cipher,
|
|
1099
|
-
wrapKey,
|
|
1100
|
-
aad,
|
|
1101
|
-
options.encryptedBlob.nonce
|
|
1102
|
-
);
|
|
1103
|
-
const keypair = deriveEvmKey({ masterSeed });
|
|
1104
|
-
unwrappedKeypair = {
|
|
1105
|
-
privateKey: keypair.privateKey,
|
|
1106
|
-
publicKey: keypair.publicKey,
|
|
1107
|
-
address: keypair.address
|
|
1108
|
-
};
|
|
1109
|
-
zeroize(masterSeed);
|
|
1110
|
-
zeroize(wrapKey);
|
|
1110
|
+
if (unwrappedKeypair && isSessionExpired()) {
|
|
1111
|
+
console.log("[PasskeyProvider] Session expired, re-authenticating");
|
|
1112
|
+
await lock();
|
|
1111
1113
|
}
|
|
1114
|
+
console.log("[PasskeyProvider] Triggering WebAuthn prompt for hardware-backed authentication");
|
|
1115
|
+
const prfSalt = deriveWrapKey(options.prfInput);
|
|
1116
|
+
const { prfOutput } = await passkey.authenticate({
|
|
1117
|
+
salt: prfSalt,
|
|
1118
|
+
credentialId: options.prfInput.credentialId
|
|
1119
|
+
});
|
|
1120
|
+
const wrapKey = prfOutput;
|
|
1121
|
+
const aad = options.aad || new TextEncoder().encode("volr/master-seed/v1");
|
|
1122
|
+
const masterSeed = await unsealMasterSeed(
|
|
1123
|
+
options.encryptedBlob.cipher,
|
|
1124
|
+
wrapKey,
|
|
1125
|
+
aad,
|
|
1126
|
+
options.encryptedBlob.nonce
|
|
1127
|
+
);
|
|
1128
|
+
const keypair = deriveEvmKey({ masterSeed });
|
|
1129
|
+
unwrappedKeypair = {
|
|
1130
|
+
privateKey: keypair.privateKey,
|
|
1131
|
+
publicKey: keypair.publicKey,
|
|
1132
|
+
address: keypair.address
|
|
1133
|
+
};
|
|
1134
|
+
zeroize(masterSeed);
|
|
1135
|
+
zeroize(wrapKey);
|
|
1136
|
+
zeroize(prfSalt);
|
|
1137
|
+
resetSessionTimer();
|
|
1112
1138
|
};
|
|
1113
1139
|
const getAddress = async () => {
|
|
1114
|
-
await ensureSession();
|
|
1140
|
+
await ensureSession({ });
|
|
1115
1141
|
if (!unwrappedKeypair) {
|
|
1116
1142
|
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1117
1143
|
}
|
|
@@ -1121,11 +1147,15 @@ function createPasskeyProvider(passkey, options) {
|
|
|
1121
1147
|
if (hash32.length !== 32) {
|
|
1122
1148
|
throw new Error(`${ERR_INVALID_PARAM}: Message hash must be 32 bytes, got ${hash32.length}`);
|
|
1123
1149
|
}
|
|
1124
|
-
|
|
1150
|
+
const shouldForceFresh = sessionTtlMs === 0;
|
|
1151
|
+
await ensureSession({ force: shouldForceFresh });
|
|
1125
1152
|
if (!unwrappedKeypair) {
|
|
1126
1153
|
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1127
1154
|
}
|
|
1128
1155
|
const sig = evmSign(unwrappedKeypair.privateKey, hash32);
|
|
1156
|
+
if (shouldForceFresh) {
|
|
1157
|
+
await lock();
|
|
1158
|
+
}
|
|
1129
1159
|
return {
|
|
1130
1160
|
r: sig.r,
|
|
1131
1161
|
s: sig.s,
|
|
@@ -1133,7 +1163,8 @@ function createPasskeyProvider(passkey, options) {
|
|
|
1133
1163
|
};
|
|
1134
1164
|
};
|
|
1135
1165
|
const signTypedData = async (input) => {
|
|
1136
|
-
|
|
1166
|
+
const shouldForceFresh = sessionTtlMs === 0;
|
|
1167
|
+
await ensureSession({ force: shouldForceFresh });
|
|
1137
1168
|
if (!unwrappedKeypair) {
|
|
1138
1169
|
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1139
1170
|
}
|
|
@@ -1149,7 +1180,10 @@ function createPasskeyProvider(passkey, options) {
|
|
|
1149
1180
|
for (let i = 0; i < 32; i++) {
|
|
1150
1181
|
msgHashBytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
1151
1182
|
}
|
|
1152
|
-
const sig =
|
|
1183
|
+
const sig = evmSign(unwrappedKeypair.privateKey, msgHashBytes);
|
|
1184
|
+
if (shouldForceFresh) {
|
|
1185
|
+
await lock();
|
|
1186
|
+
}
|
|
1153
1187
|
const v = sig.yParity + 27;
|
|
1154
1188
|
const rHex = Array.from(sig.r).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1155
1189
|
const sHex = Array.from(sig.s).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
@@ -1157,20 +1191,12 @@ function createPasskeyProvider(passkey, options) {
|
|
|
1157
1191
|
return `0x${rHex}${sHex}${vHex}`;
|
|
1158
1192
|
};
|
|
1159
1193
|
const getPublicKey = async () => {
|
|
1160
|
-
await ensureSession();
|
|
1194
|
+
await ensureSession({ });
|
|
1161
1195
|
if (!unwrappedKeypair) {
|
|
1162
1196
|
throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
|
|
1163
1197
|
}
|
|
1164
1198
|
return new Uint8Array(unwrappedKeypair.publicKey);
|
|
1165
1199
|
};
|
|
1166
|
-
const lock = async () => {
|
|
1167
|
-
if (unwrappedKeypair) {
|
|
1168
|
-
console.log("[PasskeyProvider] lock(): zeroizing sensitive data from memory");
|
|
1169
|
-
zeroize(unwrappedKeypair.privateKey);
|
|
1170
|
-
zeroize(unwrappedKeypair.publicKey);
|
|
1171
|
-
unwrappedKeypair = null;
|
|
1172
|
-
}
|
|
1173
|
-
};
|
|
1174
1200
|
return {
|
|
1175
1201
|
keyStorageType: "passkey",
|
|
1176
1202
|
ensureSession,
|
|
@@ -1324,6 +1350,7 @@ exports.deriveEvmKey = deriveEvmKey;
|
|
|
1324
1350
|
exports.deriveWrapKey = deriveWrapKey;
|
|
1325
1351
|
exports.evmSign = evmSign;
|
|
1326
1352
|
exports.evmVerify = evmVerify;
|
|
1353
|
+
exports.generateNonce = generateNonce;
|
|
1327
1354
|
exports.getAuthNonce = getAuthNonce;
|
|
1328
1355
|
exports.getRandomBytes = getRandomBytes;
|
|
1329
1356
|
exports.hkdfSha256 = hkdfSha256;
|