@volr/sdk-core 0.1.64 → 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 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 || getRandomBytes(12);
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 ensureSession = async (opts) => {
1060
- if (opts?.force && unwrappedKeypair) {
1061
- console.log("[PasskeyProvider] force=true: zeroizing existing session");
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
- if (unwrappedKeypair) {
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 (opts?.interactive || opts?.force) {
1070
- console.log("[PasskeyProvider] interactive mode: triggering WebAuthn prompt");
1071
- const prfSalt = deriveWrapKey(options.prfInput);
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
- await ensureSession();
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
- await ensureSession();
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 = await signMessage(msgHashBytes);
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;