@volr/sdk-core 0.1.65 → 0.1.67

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 false;
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,7 +1147,7 @@ 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
+ await ensureSession({ });
1125
1151
  if (!unwrappedKeypair) {
1126
1152
  throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
1127
1153
  }
@@ -1133,7 +1159,7 @@ function createPasskeyProvider(passkey, options) {
1133
1159
  };
1134
1160
  };
1135
1161
  const signTypedData = async (input) => {
1136
- await ensureSession();
1162
+ await ensureSession({ });
1137
1163
  if (!unwrappedKeypair) {
1138
1164
  throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
1139
1165
  }
@@ -1149,7 +1175,7 @@ function createPasskeyProvider(passkey, options) {
1149
1175
  for (let i = 0; i < 32; i++) {
1150
1176
  msgHashBytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
1151
1177
  }
1152
- const sig = await signMessage(msgHashBytes);
1178
+ const sig = evmSign(unwrappedKeypair.privateKey, msgHashBytes);
1153
1179
  const v = sig.yParity + 27;
1154
1180
  const rHex = Array.from(sig.r).map((b) => b.toString(16).padStart(2, "0")).join("");
1155
1181
  const sHex = Array.from(sig.s).map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -1157,20 +1183,12 @@ function createPasskeyProvider(passkey, options) {
1157
1183
  return `0x${rHex}${sHex}${vHex}`;
1158
1184
  };
1159
1185
  const getPublicKey = async () => {
1160
- await ensureSession();
1186
+ await ensureSession({ });
1161
1187
  if (!unwrappedKeypair) {
1162
1188
  throw new Error(`${ERR_INVALID_PARAM}: Session not established`);
1163
1189
  }
1164
1190
  return new Uint8Array(unwrappedKeypair.publicKey);
1165
1191
  };
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
1192
  return {
1175
1193
  keyStorageType: "passkey",
1176
1194
  ensureSession,
@@ -1324,6 +1342,7 @@ exports.deriveEvmKey = deriveEvmKey;
1324
1342
  exports.deriveWrapKey = deriveWrapKey;
1325
1343
  exports.evmSign = evmSign;
1326
1344
  exports.evmVerify = evmVerify;
1345
+ exports.generateNonce = generateNonce;
1327
1346
  exports.getAuthNonce = getAuthNonce;
1328
1347
  exports.getRandomBytes = getRandomBytes;
1329
1348
  exports.hkdfSha256 = hkdfSha256;