w3pk 0.7.0 → 0.7.2
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 +76 -19
- package/dist/index.d.mts +650 -5
- package/dist/index.d.ts +650 -5
- package/dist/index.js +776 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +776 -5
- package/dist/index.mjs.map +1 -1
- package/docs/BUNDLE_SIZES.md +7 -4
- package/docs/MIGRATION.md +15 -15
- package/docs/QR_CODE.md +1887 -0
- package/docs/RECOVERY.md +992 -0
- package/docs/SECURITY.md +631 -0
- package/docs/ZK_INTEGRATION_GUIDE.md +6 -4
- package/docs/index.html +4 -3
- package/package.json +9 -2
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,777 @@
|
|
|
1
|
-
var Ae=Object.defineProperty;var S=(o,e)=>()=>(o&&(e=o(o=0)),e);var re=(o,e)=>{for(var t in e)Ae(o,t,{get:e[t],enumerable:!0})};var d,b,I,h,l,u,L,p=S(()=>{"use strict";d=class extends Error{constructor(t,r,n){super(t);this.code=r;this.originalError=n;this.name="Web3PasskeyError"}},b=class extends d{constructor(e,t){super(e,"AUTHENTICATION_ERROR",t),this.name="AuthenticationError"}},I=class extends d{constructor(e,t){super(e,"REGISTRATION_ERROR",t),this.name="RegistrationError"}},h=class extends d{constructor(e,t){super(e,"WALLET_ERROR",t),this.name="WalletError"}},l=class extends d{constructor(e,t){super(e,"CRYPTO_ERROR",t),this.name="CryptoError"}},u=class extends d{constructor(e,t){super(e,"STORAGE_ERROR",t),this.name="StorageError"}},L=class extends d{constructor(t,r,n){super(t,"API_ERROR",n);this.statusCode=r;this.name="ApiError"}}});var F={};re(F,{CredentialStorage:()=>P});var k,T,P,A=S(()=>{"use strict";p();k="w3pk_credential_",T="w3pk_credential_index",P=class{constructor(e){if(e)this.storage=e;else if(typeof window<"u"&&window.localStorage)this.storage=window.localStorage;else throw new u("localStorage is not available")}saveCredential(e){try{let t=`${k}${e.id}`;this.storage.setItem(t,JSON.stringify(e)),this.addToIndex(e.id)}catch(t){throw new u("Failed to save credential",t)}}getCredentialById(e){try{let t=`${k}${e}`,r=this.storage.getItem(t);return r?JSON.parse(r):null}catch(t){throw new u("Failed to retrieve credential",t)}}getCredentialByUsername(e){try{return this.getAllCredentials().find(r=>r.username===e)||null}catch(t){throw new u("Failed to retrieve credential",t)}}getCredentialByAddress(e){try{return this.getAllCredentials().find(r=>r.ethereumAddress.toLowerCase()===e.toLowerCase())||null}catch(t){throw new u("Failed to retrieve credential",t)}}getAllCredentials(){try{return this.getIndex().map(t=>this.getCredentialById(t)).filter(t=>t!==null)}catch(e){throw new u("Failed to retrieve credentials",e)}}userExists(e){return this.getCredentialByUsername(e)!==null}updateLastUsed(e){try{let t=this.getCredentialById(e);t&&(t.lastUsed=Date.now(),this.saveCredential(t))}catch(t){throw new u("Failed to update timestamp",t)}}deleteCredential(e){try{let t=`${k}${e}`;this.storage.removeItem(t),this.removeFromIndex(e)}catch(t){throw new u("Failed to delete credential",t)}}clearAll(){try{this.getIndex().forEach(t=>{let r=`${k}${t}`;this.storage.removeItem(r)}),this.storage.removeItem(T)}catch(e){throw new u("Failed to clear credentials",e)}}getIndex(){try{let e=this.storage.getItem(T);return e?JSON.parse(e):[]}catch{return[]}}addToIndex(e){let t=this.getIndex();t.includes(e)||(t.push(e),this.storage.setItem(T,JSON.stringify(t)))}removeFromIndex(e){let r=this.getIndex().filter(n=>n!==e);this.storage.setItem(T,JSON.stringify(r))}}});function C(o){let e=0n;for(let t=0;t<o.length;t++)e=e<<8n|BigInt(o[t]);return e}var Q=S(()=>{"use strict"});var B,me=S(()=>{"use strict";p();Q();B=class{constructor(){this.circuits=new Map;this.loadSnarkJS()}async loadSnarkJS(){try{this.snarkjs=await import("snarkjs")}catch{this.snarkjs=null}}registerCircuit(e,t){this.circuits.set(e,t)}async generateMembershipProof(e){let t=this.circuits.get("membership");if(!t)throw new l("Membership circuit not registered");let r={leaf:e.value,pathIndices:e.pathIndices,pathElements:e.pathElements,root:e.root};return this.generateProof("membership",t,r)}async generateThresholdProof(e){let t=this.circuits.get("threshold");if(!t)throw new l("Threshold circuit not registered");let r={value:e.value.toString(),blinding:e.blinding.toString(),threshold:e.threshold.toString(),commitment:e.commitment};return this.generateProof("threshold",t,r)}async generateRangeProof(e){let t=this.circuits.get("range");if(!t)throw new l("Range circuit not registered");let r={value:e.value.toString(),blinding:e.blinding.toString(),min:e.min.toString(),max:e.max.toString(),commitment:e.commitment};return this.generateProof("range",t,r)}async generateOwnershipProof(e){let t=this.circuits.get("ownership");if(!t)throw new l("Ownership circuit not registered");let r={privateKey:e.privateKey,nonce:e.nonce.toString(),address:e.address,challenge:e.challenge};return this.generateProof("ownership",t,r)}async generateNFTOwnershipProof(e){let t=this.circuits.get("nft");if(!t)throw new l("NFT ownership circuit not registered");let r=await this.getPoseidonHash(),n=e.ownerAddress.startsWith("0x")?e.ownerAddress:"0x"+e.ownerAddress,s=e.contractAddress.startsWith("0x")?e.contractAddress:"0x"+e.contractAddress,i=r([BigInt(n)]),c=r([BigInt(s)]),a=i instanceof Uint8Array?C(i):i,g=c instanceof Uint8Array?C(c):c,y={ownerAddress:a.toString(),pathElements:e.pathElements,pathIndices:e.pathIndices,root:e.holdersRoot,contractAddress:g.toString(),minBalance:(e.minBalance||1n).toString()};return this.generateProof("nft",t,y)}async generateProof(e,t,r){try{if(!this.snarkjs&&(await this.loadSnarkJS(),!this.snarkjs))throw new l(`ZK proofs require snarkjs. Install with: npm install snarkjs
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
For more info: https://github.com/w3hc/w3pk#zero-knowledge-proofs`,g)}}parsePublicSignals(e,t){switch(e){case"membership":return{root:t[0]};case"threshold":return{commitment:t[0],threshold:t[1]};case"range":return{commitment:t[0],min:t[1],max:t[2]};case"ownership":return{address:t[0],challenge:t[1]};case"nft":return{holdersRoot:t[0],contractAddress:t[1],minBalance:t[2],nullifierHash:t[3]};default:return t.reduce((r,n,s)=>(r[`signal_${s}`]=n,r),{})}}isProofExpired(e,t=36e5){return Date.now()-e.timestamp>t}}});var fe={};re(fe,{ZKProofModule:()=>ee});var ee,ye=S(()=>{"use strict";p();me();pe();ee=class{constructor(e={}){this.config=e,this.generator=new B,this.verifier=new Z,this.initializeCircuits()}async initializeCircuits(){}async proveMembership(e){try{return this.assertProofEnabled("membership"),await this.generator.generateMembershipProof(e)}catch(t){throw new d("Failed to generate membership proof","ZK_MEMBERSHIP_ERROR",t)}}async proveThreshold(e){try{return this.assertProofEnabled("threshold"),await this.generator.generateThresholdProof(e)}catch(t){throw new d("Failed to generate threshold proof","ZK_THRESHOLD_ERROR",t)}}async proveRange(e){try{return this.assertProofEnabled("range"),await this.generator.generateRangeProof(e)}catch(t){throw new d("Failed to generate range proof","ZK_RANGE_ERROR",t)}}async proveOwnership(e){try{return this.assertProofEnabled("ownership"),await this.generator.generateOwnershipProof(e)}catch(t){throw new d("Failed to generate ownership proof","ZK_OWNERSHIP_ERROR",t)}}async proveNFTOwnership(e){try{return this.assertProofEnabled("nft"),await this.generator.generateNFTOwnershipProof(e)}catch(t){throw new d("Failed to generate NFT ownership proof","ZK_NFT_OWNERSHIP_ERROR",t)}}async verify(e){try{return await this.verifier.verifyProof(e)}catch(t){throw new d("Failed to verify proof","ZK_VERIFICATION_ERROR",t)}}async verifyBatch(e){try{return await this.verifier.verifyBatch(e)}catch(t){throw new d("Failed to verify proofs","ZK_BATCH_VERIFICATION_ERROR",t)}}async verifyMembership(e,t){try{return await this.verifier.verifyMembershipProof(e,t)}catch(r){throw new d("Failed to verify membership proof","ZK_MEMBERSHIP_VERIFICATION_ERROR",r)}}async verifyThreshold(e,t,r){try{return await this.verifier.verifyThresholdProof(e,t,r)}catch(n){throw new d("Failed to verify threshold proof","ZK_THRESHOLD_VERIFICATION_ERROR",n)}}async verifyRange(e,t,r,n){try{return await this.verifier.verifyRangeProof(e,t,r,n)}catch(s){throw new d("Failed to verify range proof","ZK_RANGE_VERIFICATION_ERROR",s)}}async verifyOwnership(e,t,r){try{return await this.verifier.verifyOwnershipProof(e,t,r)}catch(n){throw new d("Failed to verify ownership proof","ZK_OWNERSHIP_VERIFICATION_ERROR",n)}}async verifyNFTOwnership(e,t,r,n=1n){try{return await this.verifier.verifyNFTOwnershipProof(e,t,r,n)}catch(s){throw new d("Failed to verify NFT ownership proof","ZK_NFT_OWNERSHIP_VERIFICATION_ERROR",s)}}async createCommitment(e,t){try{return await this.generator.createCommitment(e,t)}catch(r){throw new d("Failed to create commitment","ZK_COMMITMENT_ERROR",r)}}async computeMerkleRoot(e,t,r){try{return await this.generator.computeMerkleRoot(e,t,r)}catch(n){throw new d("Failed to compute merkle root","ZK_MERKLE_ERROR",n)}}registerCircuit(e,t){this.generator.registerCircuit(e,t),this.verifier.registerCircuit(e,t)}assertProofEnabled(e){if(this.config.enabledProofs&&!this.config.enabledProofs.includes(e))throw new d(`Proof type '${e}' is not enabled`,"ZK_PROOF_DISABLED")}get isAvailable(){return!0}}});p();import{startRegistration as Ie}from"@simplewebauthn/browser";function ve(o){return/^0x[a-fA-F0-9]{40}$/.test(o)}function Se(o){return o.length>=3&&o.length<=50}function ne(o){if(!ve(o))throw new Error("Invalid Ethereum address format")}function oe(o){if(!Se(o))throw new Error("Username must be between 3 and 50 characters")}A();function Fe(){let o=new Uint8Array(32);return crypto.getRandomValues(o),btoa(String.fromCharCode(...o)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function se(o){try{let{username:e,ethereumAddress:t}=o;oe(e),ne(t);let r=new P;if(r.userExists(e))throw new Error("Username already registered");let s={challenge:Fe(),rp:{name:"w3pk",id:window.location.hostname},user:{id:e,name:e,displayName:e},pubKeyCredParams:[{type:"public-key",alg:-7},{type:"public-key",alg:-257}],authenticatorSelection:{authenticatorAttachment:"platform",userVerification:"required",residentKey:"required",requireResidentKey:!0},timeout:6e4,attestation:"none"},i=await Ie({optionsJSON:s}),c=i.response.publicKey;if(!c)throw new Error("Public key not returned from authenticator");r.saveCredential({id:i.id,publicKey:c,username:e,ethereumAddress:t,createdAt:Date.now(),lastUsed:Date.now()}),console.log("[register] Credential response:",i.response);let a=i.response.attestationObject;if(console.log("[register] Attestation object:",a),!a)throw new Error("Attestation object not returned from authenticator");let g=Ee(a);return console.log("[register] Attestation buffer length:",g.byteLength),{signature:g}}catch(e){throw new I(e instanceof Error?e.message:"Registration failed",e)}}function Ee(o){let e=o.replace(/-/g,"+").replace(/_/g,"/"),t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r.buffer}p();A();import{startAuthentication as xe}from"@simplewebauthn/browser";function Ke(){let o=new Uint8Array(32);return crypto.getRandomValues(o),btoa(String.fromCharCode(...o)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function O(){try{let o=new P,t={challenge:Ke(),rpId:window.location.hostname,userVerification:"required",timeout:6e4},r=await xe({optionsJSON:t}),n=o.getCredentialById(r.id);if(!n)throw new Error("Credential not found");if(!await Ce(r,n))throw new Error("Signature verification failed");o.updateLastUsed(n.id);let i=M(r.response.signature);return{verified:!0,user:{username:n.username,ethereumAddress:n.ethereumAddress,credentialId:n.id},signature:i}}catch(o){throw new b(o instanceof Error?o.message:"Authentication failed",o)}}async function Ce(o,e){try{let t=M(e.publicKey),r=await crypto.subtle.importKey("spki",t,{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]),n=M(o.response.authenticatorData),s=o.response.clientDataJSON,i;s.startsWith("eyJ")?i=atob(s.replace(/-/g,"+").replace(/_/g,"/")):i=s;let c=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(i)),a=new Uint8Array(n.byteLength+c.byteLength);a.set(new Uint8Array(n),0),a.set(new Uint8Array(c),n.byteLength);let g=M(o.response.signature),y=Re(new Uint8Array(g));return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},r,y,a)}catch(t){return console.error("Signature verification error:",t),!1}}function M(o){let e=o.replace(/-/g,"+").replace(/_/g,"/"),t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r.buffer}function Re(o){let e=2;e++;let t=o[e++];t>32&&(e++,t--);let r=o.slice(e,e+t);e+=t,e++;let n=o[e++];n>32&&(e++,n--);let s=o.slice(e,e+n),i=new Uint8Array(64);return i.set(r,32-r.length),i.set(s,64-s.length),i.buffer}p();var ke="Web3PasskeyWallet",Te=1,f="wallets",_=class{constructor(){this.db=null}async init(){return new Promise((e,t)=>{let r=indexedDB.open(ke,Te);r.onerror=()=>t(new u("Failed to open database",r.error)),r.onsuccess=()=>{this.db=r.result,e()},r.onupgradeneeded=()=>{let n=r.result;n.objectStoreNames.contains(f)||n.createObjectStore(f,{keyPath:"ethereumAddress"})}})}async store(e){return this.db||await this.init(),new Promise((t,r)=>{let i=this.db.transaction([f],"readwrite").objectStore(f).put(e);i.onerror=()=>r(new u("Failed to store wallet data",i.error)),i.onsuccess=()=>t()})}async retrieve(e){return this.db||await this.init(),new Promise((t,r)=>{let i=this.db.transaction([f],"readonly").objectStore(f).get(e);i.onerror=()=>r(new u("Failed to retrieve wallet data",i.error)),i.onsuccess=()=>t(i.result||null)})}async delete(e){return this.db||await this.init(),new Promise((t,r)=>{let i=this.db.transaction([f],"readwrite").objectStore(f).delete(e);i.onerror=()=>r(new u("Failed to delete wallet data",i.error)),i.onsuccess=()=>t()})}async clear(){return this.db||await this.init(),new Promise((e,t)=>{let s=this.db.transaction([f],"readwrite").objectStore(f).clear();s.onerror=()=>t(new u("Failed to clear wallet data",s.error)),s.onsuccess=()=>e()})}};p();import{ethers as U}from"ethers";function $(){try{let o=U.Wallet.createRandom().mnemonic;if(!o)throw new Error("Failed to generate mnemonic");let e=o.phrase;return{address:U.HDNodeWallet.fromPhrase(e,void 0,"m/44'/60'/0'/0/0").address,mnemonic:e}}catch(o){throw new h("Wallet generation failed",o)}}function Me(o){try{if(!o||o.trim().split(/\s+/).length<12)throw new Error("Invalid mnemonic: must be at least 12 words");return U.HDNodeWallet.fromPhrase(o.trim(),void 0,"m/44'/60'/0'/0/0")}catch(e){throw new h(`Wallet creation failed: ${e instanceof Error?e.message:"Invalid mnemonic"}`,e)}}function q(o,e=0){try{if(!o||o.trim().split(/\s+/).length<12)throw new Error("Invalid mnemonic: must be at least 12 words");if(e<0||!Number.isInteger(e))throw new Error("Index must be a non-negative integer");let t=`m/44'/60'/0'/0/${e}`,r=U.HDNodeWallet.fromPhrase(o.trim(),void 0,t);return{address:r.address,privateKey:r.privateKey}}catch(t){throw new h(`HD wallet derivation failed: ${t instanceof Error?t.message:"Unknown error"}`,t)}}p();async function E(o,e){try{let t=e?`w3pk-v4:${o}:${e}`:`w3pk-v4:${o}`,r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)),n=await crypto.subtle.importKey("raw",r,{name:"PBKDF2"},!1,["deriveKey"]),s=await crypto.subtle.digest("SHA-256",new TextEncoder().encode("w3pk-salt-v4"));return crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(s),iterations:21e4,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}catch(t){throw new l("Failed to derive encryption key from WebAuthn",t)}}async function V(o,e){try{let t=crypto.getRandomValues(new Uint8Array(12)),r=new TextEncoder().encode(o),n=await crypto.subtle.encrypt({name:"AES-GCM",iv:t},e,r),s=new Uint8Array(t.length+n.byteLength);return s.set(t),s.set(new Uint8Array(n),t.length),btoa(String.fromCharCode(...s))}catch(t){throw new l("Failed to encrypt data",t)}}async function z(o,e){try{if(!o||o.length<16)throw new Error("Invalid encrypted data: too small");let t=new Uint8Array(atob(o).split("").map(i=>i.charCodeAt(0)));if(t.length<12)throw new Error("Invalid encrypted data: missing IV");let r=t.slice(0,12),n=t.slice(12);if(n.length===0)throw new Error("Invalid encrypted data: no content");let s=await crypto.subtle.decrypt({name:"AES-GCM",iv:r},e,n);return new TextDecoder().decode(s)}catch(t){throw new l(`Data decryption failed: ${t instanceof Error?t.message:"Unknown error"}`,t)}}p();p();import{ethers as m}from"ethers";function x(o){try{let e=m.HDNodeWallet.fromPhrase(o,void 0,"m/44'/60'/1'/0/0"),t=m.HDNodeWallet.fromPhrase(o,void 0,"m/44'/60'/1'/0/1"),r=t.signingKey.compressedPublicKey,n=e.signingKey.compressedPublicKey,s=r+n.slice(2),i=_e(e.signingKey.publicKey,t.signingKey.publicKey);return{stealthMetaAddress:s,spendingPubKey:r,viewingPubKey:n,viewingKey:e.privateKey,spendingKey:t.privateKey,metaAddress:i}}catch(e){throw new l("Failed to derive stealth keys",e)}}function J(o){try{let e="0x"+o.slice(2,68),t="0x"+o.slice(68),r=m.Wallet.createRandom(),n=r.signingKey.compressedPublicKey,s=Y(r.privateKey,t),i=m.keccak256(s),c="0x"+i.slice(2,4),a=le(e,ce(i));return{stealthAddress:de(a),ephemeralPubKey:n,viewTag:c,ephemeralPubkey:n}}catch(e){throw new l("Failed to generate stealth address",e)}}function N(o,e,t,r,n){try{let s=Y(o,t),i=m.keccak256(s);if(n&&("0x"+i.slice(2,4)).toLowerCase()!==n.toLowerCase())return{isForUser:!1};let c=le(e,ce(i)),a=de(c);return a.toLowerCase()!==r.toLowerCase()?{isForUser:!1}:{isForUser:!0,stealthAddress:a}}catch{return{isForUser:!1}}}function G(o,e,t){try{let r=Y(o,t),n=m.keccak256(r);return Oe(e,n)}catch(r){throw new l("Failed to compute stealth private key",r)}}function Y(o,e){try{return new m.Wallet(o).signingKey.computeSharedSecret(e)}catch(t){throw new l("Failed to compute shared secret",t)}}function ce(o){try{return new m.Wallet(o).signingKey.compressedPublicKey}catch(e){throw new l("Failed to multiply generator by scalar",e)}}function le(o,e){try{let t=m.SigningKey.computePublicKey(o,!1),r=m.SigningKey.computePublicKey(e,!1),n=BigInt("0x"+t.slice(4,68)),s=BigInt("0x"+t.slice(68)),i=BigInt("0x"+r.slice(4,68)),c=BigInt("0x"+r.slice(68)),a=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");if(n===i&&s===c){let we=3n*n*n%a,be=2n*s%a,j=we*ae(be,a)%a,te=(j*j-2n*n)%a,Pe=(j*(n-te)-s)%a;return ie((te+a)%a,(Pe+a)%a)}let g=((c-s)%a+a)%a,y=((i-n)%a+a)%a,v=g*ae(y,a)%a,w=(v*v-n-i)%a,H=(v*(n-w)-s)%a;return ie((w+a)%a,(H+a)%a)}catch(t){throw new l("Failed to add public keys",t)}}function Oe(o,e){try{let t=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),r=BigInt(o),n=BigInt(e);return"0x"+((r+n)%t).toString(16).padStart(64,"0")}catch(t){throw new l("Failed to add private keys",t)}}function ie(o,e){return"0x"+(e%2n===0n?"02":"03")+o.toString(16).padStart(64,"0")}function ae(o,e){o=(o%e+e)%e;let[t,r]=[o,e],[n,s]=[1n,0n];for(;r!==0n;){let i=t/r;[t,r]=[r,t-i*r],[n,s]=[s,n-i*s]}return(n%e+e)%e}function de(o){try{let e=m.SigningKey.computePublicKey(o,!1),t=m.keccak256("0x"+e.slice(4));return m.getAddress("0x"+t.slice(-40))}catch(e){throw new l("Failed to derive address from public key",e)}}function _e(o,e){let t=m.solidityPackedKeccak256(["bytes","bytes"],[o,e]);return m.getAddress("0x"+t.slice(26))}function Ue(o,e,t,r){let n=new m.Wallet(e);return N(o,n.signingKey.compressedPublicKey,t,r).isForUser}var K=class{constructor(e,t){this.getMnemonic=t}async generateStealthAddress(e){try{let t=await this.getMnemonic(e?.requireAuth),r=x(t),n=J(r.stealthMetaAddress);return{stealthAddress:n.stealthAddress,ephemeralPublicKey:n.ephemeralPubKey,viewTag:n.viewTag}}catch(t){throw new d("Failed to generate stealth address","STEALTH_GENERATION_ERROR",t)}}async parseAnnouncement(e,t){try{let r=await this.getMnemonic(t?.requireAuth),n=x(r),s=N(n.viewingKey,n.spendingPubKey,e.ephemeralPublicKey,e.stealthAddress,e.viewTag);if(!s.isForUser)return{isForUser:!1};let i=G(n.viewingKey,n.spendingKey,e.ephemeralPublicKey);return{isForUser:!0,stealthAddress:s.stealthAddress,stealthPrivateKey:i}}catch(r){throw new d("Failed to parse announcement","ANNOUNCEMENT_PARSE_ERROR",r)}}async scanAnnouncements(e,t){let r=[];for(let n of e){let s=await this.parseAnnouncement(n,t);s.isForUser&&r.push(s)}return r}async getKeys(e){try{let t=await this.getMnemonic(e?.requireAuth);return x(t)}catch(t){throw new d("Failed to get stealth keys","STEALTH_KEYS_ERROR",t)}}async getStealthMetaAddress(e){try{return(await this.getKeys(e)).stealthMetaAddress}catch(t){throw new d("Failed to get stealth meta-address","STEALTH_META_ADDRESS_ERROR",t)}}get isAvailable(){return!0}};var D=class{constructor(e=1){this.session=null;this.sessionDuration=e*60*60*1e3}startSession(e,t){let r=Date.now()+this.sessionDuration;this.session={mnemonic:e,expiresAt:r,credentialId:t}}getMnemonic(){return this.session?Date.now()>this.session.expiresAt?(this.clearSession(),null):this.session.mnemonic:null}getCredentialId(){return this.session?Date.now()>this.session.expiresAt?(this.clearSession(),null):this.session.credentialId:null}isActive(){return this.getMnemonic()!==null}getRemainingTime(){return this.session?Date.now()>this.session.expiresAt?(this.clearSession(),0):Math.floor((this.session.expiresAt-Date.now())/1e3):0}extendSession(){if(!this.session)throw new Error("No active session to extend");if(Date.now()>this.session.expiresAt)throw this.clearSession(),new Error("Session expired, cannot extend");this.session.expiresAt=Date.now()+this.sessionDuration}clearSession(){this.session&&(this.session.mnemonic="0".repeat(this.session.mnemonic.length)),this.session=null}setSessionDuration(e){this.sessionDuration=e*60*60*1e3}};var X={debug:!1,sessionDuration:1,onError:o=>{X.debug&&console.error("[w3pk]",o)}};p();var he="https://chainid.network/chains.json";var W=null,Ne=[/\$\{[\w_]+\}/i,/\{[\w_]+\}/i,/<[\w_]+>/i,/YOUR[-_]?API[-_]?KEY/i,/INSERT[-_]?API[-_]?KEY/i,/API[-_]?KEY[-_]?HERE/i];function De(o){return Ne.some(e=>e.test(o))}async function We(o=he){let e=await fetch(o);if(!e.ok)throw new Error(`Failed to fetch chains data: ${e.status} ${e.statusText}`);return await e.json()}async function Be(o){let e=o?.chainsJsonUrl??he,t=o?.cacheDuration??36e5,r=Date.now();if(W&&r-W.timestamp<t)return W.data;let n=await We(e);return W={data:n,timestamp:r},n}async function ue(o,e){let r=(await Be(e)).find(n=>n.chainId===o);return r?r.rpc.filter(n=>!De(n)&&!n.startsWith("wss://")&&!n.startsWith("ws://")):[]}var Ze=new Set([1,10,8453,42161,57073,100,42220,137,42,15,40,41,44,46,47,50,51,56,61,71,82,83,95,97,112,123,130,146,151,153,171,180,183,185,195,215,228,247,248,252,261,267,291,293,311,332,336,395,401,416,466,480,488,510,545,634,647,648,747,831,919,938,945,957,964,970,980,995,997,1001,1003,1024,1030,1114,1125,1135,1149,1188,1284,1285,1287,1300,1301,1315,1337,1338,1339,1424,1514,1687,1727,1729,1740,1750,1829,1868,1946,1961,1962,1969,1989,1995,2017,2020,2031,2043,2109,2241,2340,2345,2440,2522,2559,2649,3068,3109,3338,3502,3799,3888,3889,4e3,4048,4078,4162,4201,4202,4460,4488,4661,4689,4690,4888,5e3,5003,5124,5234,5330,5424,5522,6283,6342,6398,6678,6806,6934,6942,6969,7117,7171,7200,7208,7368,7518,7668,7672,7744,7771,7869,7897,8008,8118,8217,8408,8700,8726,8727,8844,8880,8881,8882,8889,9372,9496,9700,9745,9746,9899,9990,9996,10011,10085,10143,10200,11221,11501,11504,11891,13370,14853,16602,16661,17e3,18880,18881,19991,21e3,21816,21912,25327,32323,33401,34443,41923,42170,43111,44787,47805,48898,48900,49049,49088,5e4,50312,53302,53456,53457,55244,56288,59141,60808,60850,62320,62850,64002,71402,72080,73114,73115,75338,78281,80002,80008,80069,80094,80451,80931,84532,88899,91342,92278,94524,96970,97476,97477,98985,100021,100501,101010,102030,102031,102032,112358,120893,121212,121213,121214,121215,129399,161803,175188,192940,193939,198989,212013,222222,240241,325e3,355110,355113,421614,555777,560048,656476,713715,743111,747474,763373,763375,777777,806582,808813,810180,839999,888991,2019775,2222222,4278608,5734951,6666689,6985385,7080969,7777777,9999999,11142220,11155111,11155420,11155931,16969696,19850818,20180427,20250825,28122024,34949059,37084624,52164803,61022448,79479957,96969696,420420421,420420422,888888888,974399131,999999999,1020352220,1273227453,1313161560,1350216234,1380996178,1417429182,1444673419,1482601649,1564830818,2046399126,11297108099,11297108109,88153591557,123420000220,123420001114]);async function He(o,e=1e4){try{let t=new AbortController,r=setTimeout(()=>t.abort(),e),n=await fetch(o,{method:"POST",headers:{"Content-Type":"application/json"},signal:t.signal,body:JSON.stringify({jsonrpc:"2.0",method:"eth_estimateGas",params:[{from:"0xdeadbeef00000000000000000000000000000000",to:"0xdeadbeef00000000000000000000000000000000",data:"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",value:"0x0"},"latest",{"0xdeadbeef00000000000000000000000000000000":{code:"0xef01000000000000000000000000000000000000000001"}}],id:1})});if(clearTimeout(r),!n.ok)return!1;let s=await n.json();if(s.error){let i=(s.error.message||"").toLowerCase();return!["unsupported","not supported","unknown","invalid","unrecognized","does not support","not implemented"].some(a=>i.includes(a))}return s.result!==void 0}catch{return!1}}async function ge(o,e,t){if(Ze.has(o))return!0;let r=t?.maxEndpoints||3,n=t?.timeout||1e4;try{let s=await e(o);if(s.length===0)return!1;let i=s.slice(0,r);for(let c of i)if(await He(c,n))return!0;return!1}catch{return!1}}var R=class{constructor(e={}){this.currentUser=null;this.currentWallet=null;this.config={...X,...e},this.walletStorage=new _,this.sessionManager=new D(e.sessionDuration||1),e.stealthAddresses!==void 0&&(this.stealth=new K(e.stealthAddresses,t=>this.getMnemonicFromSession(t))),e.zkProofs&&this.initializeZKModule(e.zkProofs)}async initializeZKModule(e){try{let{ZKProofModule:t}=await Promise.resolve().then(()=>(ye(),fe));this.zkModule=new t(e)}catch{console.warn("ZK module not available. Install dependencies: npm install snarkjs circomlibjs")}}async getMnemonicFromSession(e=!1){if(!e){let g=this.sessionManager.getMnemonic();if(g)return g}if(!this.currentUser)throw new h("Must be authenticated. Call login() first.");let t=await this.walletStorage.retrieve(this.currentUser.ethereumAddress);if(!t)throw new h("No wallet found. Generate a wallet first.");if(!(await O()).user)throw new h("Authentication failed");let i=new(await Promise.resolve().then(()=>(A(),F))).CredentialStorage().getCredentialById(t.credentialId)?.publicKey,c=await E(t.credentialId,i),a=await z(t.encryptedMnemonic,c);return this.sessionManager.startSession(a,t.credentialId),a}async register(e){try{this.currentWallet?.address||await this.generateWallet();let t=this.currentWallet.address,r=this.currentWallet.mnemonic,n=await se({username:e.username,ethereumAddress:t});this.currentUser={id:t,username:e.username,displayName:e.username,ethereumAddress:t};let i=new(await Promise.resolve().then(()=>(A(),F))).CredentialStorage().getCredentialByAddress(t);if(!i)throw new h("Credential not found after registration");let c=i.id,a=i.publicKey,g=await E(c,a),y=await V(r,g);return await this.walletStorage.store({ethereumAddress:this.currentUser.ethereumAddress,encryptedMnemonic:y,credentialId:c,createdAt:Date.now()}),this.sessionManager.startSession(r,c),this.config.onAuthStateChanged?.(!0,this.currentUser),{mnemonic:r}}catch(t){throw this.config.onError?.(t),t}}async login(){try{let e=await O();if(!e.verified||!e.user)throw new b("Login failed");this.currentUser={id:e.user.ethereumAddress,username:e.user.username,displayName:e.user.username,ethereumAddress:e.user.ethereumAddress};let t=await this.walletStorage.retrieve(this.currentUser.ethereumAddress);if(!t)throw new h("No wallet found for this user. You may need to register first.");let s=new(await Promise.resolve().then(()=>(A(),F))).CredentialStorage().getCredentialById(t.credentialId)?.publicKey,i=await E(t.credentialId,s),c=await z(t.encryptedMnemonic,i);return this.sessionManager.startSession(c,t.credentialId),this.config.onAuthStateChanged?.(!0,this.currentUser),this.currentUser}catch(e){throw this.config.onError?.(e),e}}async logout(){this.currentUser=null,this.currentWallet=null,this.sessionManager.clearSession(),this.config.onAuthStateChanged?.(!1,void 0)}get isAuthenticated(){return this.currentUser!==null}get user(){return this.currentUser}async generateWallet(){try{let e=$();return this.currentWallet={address:e.address,mnemonic:e.mnemonic},{mnemonic:e.mnemonic}}catch(e){throw this.config.onError?.(e),new h("Failed to generate wallet",e)}}async deriveWallet(e,t){try{if(!this.currentUser)throw new h("Must be authenticated to derive wallet");let r=await this.getMnemonicFromSession(t?.requireAuth),n=q(r,e);return{address:n.address,privateKey:n.privateKey}}catch(r){throw this.config.onError?.(r),new h("Failed to derive wallet",r)}}async exportMnemonic(e){try{if(!this.currentUser)throw new h("Must be authenticated to export mnemonic");return await this.getMnemonicFromSession(e?.requireAuth)}catch(t){throw this.config.onError?.(t),new h("Failed to export mnemonic",t)}}async importMnemonic(e){try{if(!this.currentUser)throw new h("Must be authenticated to import mnemonic");if(!e||e.trim().split(/\s+/).length<12)throw new h("Invalid mnemonic: must be at least 12 words");let t=await O();if(!t.user)throw new h("Authentication failed");let r=t.user.credentialId,i=new(await Promise.resolve().then(()=>(A(),F))).CredentialStorage().getCredentialById(r)?.publicKey,c=await E(r,i),a=await V(e.trim(),c);await this.walletStorage.store({ethereumAddress:this.currentUser.ethereumAddress,encryptedMnemonic:a,credentialId:r,createdAt:Date.now()}),this.currentWallet={address:this.currentUser.ethereumAddress,mnemonic:e.trim()},this.sessionManager.startSession(e.trim(),r)}catch(t){throw this.config.onError?.(t),new h("Failed to import mnemonic",t)}}async signMessage(e,t){try{if(!this.currentUser)throw new h("Must be authenticated to sign message");let r=await this.getMnemonicFromSession(t?.requireAuth),{Wallet:n}=await import("ethers");return await n.fromPhrase(r).signMessage(e)}catch(r){throw this.config.onError?.(r),new h("Failed to sign message",r)}}async getEndpoints(e){return ue(e)}async supportsEIP7702(e,t){return ge(e,this.getEndpoints.bind(this),t)}get zk(){if(!this.zkModule)throw new Error("ZK module not available. Install dependencies: npm install snarkjs circomlibjs");return this.zkModule}hasActiveSession(){return this.sessionManager.isActive()}getSessionRemainingTime(){return this.sessionManager.getRemainingTime()}extendSession(){try{this.sessionManager.extendSession()}catch(e){throw new h("Cannot extend session",e)}}clearSession(){this.sessionManager.clearSession()}setSessionDuration(e){this.sessionManager.setSessionDuration(e)}get version(){return"0.7.0"}};p();function je(o={}){return new R(o)}var jt=je;export{L as ApiError,b as AuthenticationError,l as CryptoError,I as RegistrationError,K as StealthAddressModule,u as StorageError,h as WalletError,R as Web3Passkey,d as Web3PasskeyError,Ue as canControlStealthAddress,N as checkStealthAddress,G as computeStealthPrivateKey,Me as createWalletFromMnemonic,je as createWeb3Passkey,jt as default,x as deriveStealthKeys,q as deriveWalletFromMnemonic,$ as generateBIP39Wallet,J as generateStealthAddress};
|
|
1
|
+
var ft=Object.defineProperty;var u=(s,e)=>()=>(s&&(e=s(s=0)),e);var A=(s,e)=>{for(var t in e)ft(s,t,{get:e[t],enumerable:!0})};var g,E,K,d,y,h,ge,w=u(()=>{"use strict";g=class extends Error{constructor(t,r,n){super(t);this.code=r;this.originalError=n;this.name="Web3PasskeyError"}},E=class extends g{constructor(e,t){super(e,"AUTHENTICATION_ERROR",t),this.name="AuthenticationError"}},K=class extends g{constructor(e,t){super(e,"REGISTRATION_ERROR",t),this.name="RegistrationError"}},d=class extends g{constructor(e,t){super(e,"WALLET_ERROR",t),this.name="WalletError"}},y=class extends g{constructor(e,t){super(e,"CRYPTO_ERROR",t),this.name="CryptoError"}},h=class extends g{constructor(e,t){super(e,"STORAGE_ERROR",t),this.name="StorageError"}},ge=class extends g{constructor(t,r,n){super(t,"API_ERROR",n);this.statusCode=r;this.name="ApiError"}}});var O={};A(O,{CredentialStorage:()=>P});var j,J,P,C=u(()=>{"use strict";w();j="w3pk_credential_",J="w3pk_credential_index",P=class{constructor(e){if(e)this.storage=e;else if(typeof window<"u"&&window.localStorage)this.storage=window.localStorage;else throw new h("localStorage is not available")}saveCredential(e){try{let t=`${j}${e.id}`;this.storage.setItem(t,JSON.stringify(e)),this.addToIndex(e.id)}catch(t){throw new h("Failed to save credential",t)}}getCredentialById(e){try{let t=`${j}${e}`,r=this.storage.getItem(t);return r?JSON.parse(r):null}catch(t){throw new h("Failed to retrieve credential",t)}}getCredentialByUsername(e){try{return this.getAllCredentials().find(r=>r.username===e)||null}catch(t){throw new h("Failed to retrieve credential",t)}}getCredentialByAddress(e){try{return this.getAllCredentials().find(r=>r.ethereumAddress.toLowerCase()===e.toLowerCase())||null}catch(t){throw new h("Failed to retrieve credential",t)}}getAllCredentials(){try{return this.getIndex().map(t=>this.getCredentialById(t)).filter(t=>t!==null)}catch(e){throw new h("Failed to retrieve credentials",e)}}userExists(e){return this.getCredentialByUsername(e)!==null}updateLastUsed(e){try{let t=this.getCredentialById(e);t&&(t.lastUsed=Date.now(),this.saveCredential(t))}catch(t){throw new h("Failed to update timestamp",t)}}deleteCredential(e){try{let t=`${j}${e}`;this.storage.removeItem(t),this.removeFromIndex(e)}catch(t){throw new h("Failed to delete credential",t)}}clearAll(){try{this.getIndex().forEach(t=>{let r=`${j}${t}`;this.storage.removeItem(r)}),this.storage.removeItem(J)}catch(e){throw new h("Failed to clear credentials",e)}}getIndex(){try{let e=this.storage.getItem(J);return e?JSON.parse(e):[]}catch{return[]}}addToIndex(e){let t=this.getIndex();t.includes(e)||(t.push(e),this.storage.setItem(J,JSON.stringify(t)))}removeFromIndex(e){let r=this.getIndex().filter(n=>n!==e);this.storage.setItem(J,JSON.stringify(r))}}});var Se={};A(Se,{decryptData:()=>re,deriveEncryptionKey:()=>Bt,deriveEncryptionKeyFromSignature:()=>Tt,deriveEncryptionKeyFromWebAuthn:()=>R,encryptData:()=>te,generateChallenge:()=>Dt});async function R(s,e){try{let t=e?`w3pk-v4:${s}:${e}`:`w3pk-v4:${s}`,r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)),n=await crypto.subtle.importKey("raw",r,{name:"PBKDF2"},!1,["deriveKey"]),o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode("w3pk-salt-v4"));return crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(o),iterations:21e4,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}catch(t){throw new y("Failed to derive encryption key from WebAuthn",t)}}async function Bt(s,e){try{let t=e?`w3pk-v2:${s}:${e}`:`w3pk-v2:${s}`,r=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(t)),n=await crypto.subtle.importKey("raw",r,{name:"PBKDF2"},!1,["deriveKey"]),o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode("w3pk-salt-v2"));return crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(o),iterations:21e4,hash:"SHA-256"},n,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}catch(t){throw new y("Failed to derive encryption key",t)}}async function Tt(s,e){try{if(typeof window<"u"&&window.PublicKeyCredential)return R(e);let t=await crypto.subtle.digest("SHA-256",s),r=await crypto.subtle.importKey("raw",t,{name:"PBKDF2"},!1,["deriveKey"]),n=await crypto.subtle.digest("SHA-256",new TextEncoder().encode("w3pk-salt-v3:"+e));return crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(n),iterations:21e4,hash:"SHA-256"},r,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}catch(t){throw new y("Failed to derive encryption key",t)}}function Dt(){let s=new Uint8Array(32);return crypto.getRandomValues(s),btoa(String.fromCharCode(...s)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function te(s,e){try{let t=crypto.getRandomValues(new Uint8Array(12)),r=new TextEncoder().encode(s),n=await crypto.subtle.encrypt({name:"AES-GCM",iv:t},e,r),o=new Uint8Array(t.length+n.byteLength);return o.set(t),o.set(new Uint8Array(n),t.length),btoa(String.fromCharCode(...o))}catch(t){throw new y("Failed to encrypt data",t)}}async function re(s,e){try{if(!s||s.length<16)throw new Error("Invalid encrypted data: too small");let t=new Uint8Array(atob(s).split("").map(i=>i.charCodeAt(0)));if(t.length<12)throw new Error("Invalid encrypted data: missing IV");let r=t.slice(0,12),n=t.slice(12);if(n.length===0)throw new Error("Invalid encrypted data: no content");let o=await crypto.subtle.decrypt({name:"AES-GCM",iv:r},e,n);return new TextDecoder().decode(o)}catch(t){throw new y(`Data decryption failed: ${t instanceof Error?t.message:"Unknown error"}`,t)}}var ne=u(()=>{"use strict";w()});var x,Ce=u(()=>{"use strict";x=class{constructor(){this.dbName="Web3PasskeyBackup";this.version=1;this.db=null}async init(){return typeof indexedDB>"u"?Promise.resolve():new Promise((e,t)=>{let r=indexedDB.open(this.dbName,this.version);r.onerror=()=>t(r.error),r.onsuccess=()=>{this.db=r.result,e()},r.onupgradeneeded=n=>{let o=n.target.result;if(!o.objectStoreNames.contains("backups")){let i=o.createObjectStore("backups",{keyPath:"id"});i.createIndex("ethereumAddress","ethereumAddress",{unique:!1}),i.createIndex("method","method",{unique:!1}),i.createIndex("createdAt","createdAt",{unique:!1})}}})}async storeBackupMetadata(e){return this.db||await this.init(),this.db?new Promise((t,r)=>{let i=this.db.transaction(["backups"],"readwrite").objectStore("backups").put(e);i.onsuccess=()=>t(),i.onerror=()=>r(i.error)}):Promise.resolve()}async getBackupsByAddress(e){return this.db||await this.init(),this.db?new Promise((t,r)=>{let c=this.db.transaction(["backups"],"readonly").objectStore("backups").index("ethereumAddress").getAll(e);c.onsuccess=()=>t(c.result),c.onerror=()=>r(c.error)}):Promise.resolve([])}async getBackupById(e){return this.db||await this.init(),this.db?new Promise((t,r)=>{let i=this.db.transaction(["backups"],"readonly").objectStore("backups").get(e);i.onsuccess=()=>t(i.result||null),i.onerror=()=>r(i.error)}):Promise.resolve(null)}async deleteBackup(e){return this.db||await this.init(),this.db?new Promise((t,r)=>{let i=this.db.transaction(["backups"],"readwrite").objectStore("backups").delete(e);i.onsuccess=()=>t(),i.onerror=()=>r(i.error)}):Promise.resolve()}async getBackupCountByMethod(e){return this.db||await this.init(),this.db?new Promise((t,r)=>{let c=this.db.transaction(["backups"],"readonly").objectStore("backups").index("method").count(e);c.onsuccess=()=>t(c.result),c.onerror=()=>r(c.error)}):Promise.resolve(0)}}});var xe={};A(xe,{decryptWithPassword:()=>Ze,deriveAddressChecksum:()=>b,deriveKeyFromPassword:()=>ae,encryptWithPassword:()=>B,getDeviceFingerprint:()=>f,validatePasswordStrength:()=>ce});async function ae(s,e,t=31e4){let n=new TextEncoder().encode(s),o=await crypto.subtle.importKey("raw",n,"PBKDF2",!1,["deriveBits","deriveKey"]);return crypto.subtle.deriveKey({name:"PBKDF2",salt:e,iterations:t,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}async function B(s,e,t){let n=await ae(e,t,31e4),i=new TextEncoder().encode(s),c=crypto.getRandomValues(new Uint8Array(12)),a=await crypto.subtle.encrypt({name:"AES-GCM",iv:c},n,i);return{encrypted:N(a),iv:N(c),salt:N(t),iterations:31e4}}async function Ze(s,e,t,r,n=31e4){let o=Re(t),i=await ae(e,o,n),c=Re(r),a=Re(s),l=await crypto.subtle.decrypt({name:"AES-GCM",iv:c},i,a);return new TextDecoder().decode(l)}async function f(){let e=[navigator.userAgent,navigator.language,new Date().getTimezoneOffset().toString(),screen.width+"x"+screen.height,screen.colorDepth.toString()].join("|"),r=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",r);return N(n)}async function b(s){let t=new TextEncoder().encode(s.toLowerCase()),r=await crypto.subtle.digest("SHA-256",t);return N(r).substring(0,16)}function ce(s){let e=[],t=0;return s.length<12?e.push("Password must be at least 12 characters"):t+=25,/[A-Z]/.test(s)?t+=15:e.push("Add at least one uppercase letter"),/[a-z]/.test(s)?t+=15:e.push("Add at least one lowercase letter"),/[0-9]/.test(s)?t+=15:e.push("Add at least one number"),/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(s)?t+=15:e.push("Add at least one special character"),s.length>=16&&(t+=10),s.length>=20&&(t+=5),["password","12345678","qwerty","abc123","password123","admin","letmein"].some(n=>s.toLowerCase().includes(n))&&(e.push("Password is too common"),t=Math.min(t,25)),{valid:t>=50&&e.length===0,score:Math.min(t,100),feedback:e}}function N(s){let e=s instanceof Uint8Array?s:new Uint8Array(s),t="";for(let r=0;r<e.byteLength;r++)t+=String.fromCharCode(e[r]);return btoa(t)}function Re(s){let e=atob(s),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}var S=u(()=>{"use strict"});var T,Ie=u(()=>{"use strict";S();T=class{async createZipBackup(e,t,r){let n=ce(r.password);if(!n.valid)throw new Error(`Weak password: ${n.feedback.join(", ")}`);let o=crypto.getRandomValues(new Uint8Array(32)),i=await B(e,r.password,o),c=r.deviceBinding?await f():void 0,a={id:crypto.randomUUID(),ethereumAddress:t,method:"zip",createdAt:Date.now(),deviceFingerprint:c,addressChecksum:await b(t)},l={version:1,encrypted:i.encrypted,iv:i.iv,salt:i.salt,iterations:i.iterations,metadata:a,deviceFingerprint:c},p=new Map;return p.set("recovery-phrase.txt.enc",JSON.stringify(l,null,2)),p.set("metadata.json",JSON.stringify({address:t,createdAt:new Date(a.createdAt).toISOString(),backupId:a.id,addressChecksum:a.addressChecksum},null,2)),r.includeInstructions!==!1&&p.set("RECOVERY_INSTRUCTIONS.txt",this.getRecoveryInstructions()),{blob:await this.createSimpleZip(p),metadata:a}}async createSimpleZip(e){let t={};return e.forEach((r,n)=>{t[n]=r}),new Blob([JSON.stringify(t,null,2)],{type:"application/json"})}getRecoveryInstructions(){return`
|
|
2
|
+
W3PK WALLET RECOVERY INSTRUCTIONS
|
|
3
|
+
=================================
|
|
4
|
+
|
|
5
|
+
This backup contains your encrypted wallet recovery phrase.
|
|
6
|
+
|
|
7
|
+
IMPORTANT:
|
|
8
|
+
- Keep this file safe and secure
|
|
9
|
+
- You NEED the password you set during backup creation
|
|
10
|
+
- Without the password, this backup cannot be decrypted
|
|
11
|
+
|
|
12
|
+
RECOVERY STEPS:
|
|
13
|
+
--------------
|
|
14
|
+
|
|
15
|
+
1. Go to the w3pk recovery page
|
|
16
|
+
URL: https://your-w3pk-app.com/recover
|
|
17
|
+
|
|
18
|
+
2. Click "Import from Backup"
|
|
19
|
+
|
|
20
|
+
3. Upload this backup file
|
|
21
|
+
|
|
22
|
+
4. Enter your backup password
|
|
23
|
+
|
|
24
|
+
5. System will decrypt and verify your wallet
|
|
25
|
+
|
|
26
|
+
6. Your wallet will be restored!
|
|
27
|
+
|
|
28
|
+
VERIFICATION:
|
|
29
|
+
------------
|
|
30
|
+
|
|
31
|
+
After recovery, verify that the Ethereum address matches:
|
|
32
|
+
- Check the address in metadata.json
|
|
33
|
+
- Compare with your known wallet address
|
|
34
|
+
- If they match, recovery was successful \u2713
|
|
35
|
+
|
|
36
|
+
SECURITY NOTES:
|
|
37
|
+
--------------
|
|
38
|
+
|
|
39
|
+
\u2713 This backup is encrypted with AES-256-GCM
|
|
40
|
+
\u2713 Your password is required to decrypt
|
|
41
|
+
\u2713 Store this backup in a secure location:
|
|
42
|
+
- Password manager (1Password, Bitwarden)
|
|
43
|
+
- Encrypted cloud storage (Google Drive, Dropbox)
|
|
44
|
+
- USB drive in safe
|
|
45
|
+
- Physical storage in safe deposit box
|
|
46
|
+
|
|
47
|
+
\u2717 Do NOT store in:
|
|
48
|
+
- Unencrypted email
|
|
49
|
+
- Shared drives
|
|
50
|
+
- Public cloud without password protection
|
|
51
|
+
|
|
52
|
+
ALTERNATIVE RECOVERY:
|
|
53
|
+
-------------------
|
|
54
|
+
|
|
55
|
+
If you lose this backup, you can still recover using:
|
|
56
|
+
- Your 12-word recovery phrase (if saved separately)
|
|
57
|
+
- Social recovery (if configured)
|
|
58
|
+
- Passkey sync (if enabled on another device)
|
|
59
|
+
|
|
60
|
+
NEED HELP?
|
|
61
|
+
---------
|
|
62
|
+
|
|
63
|
+
Visit: https://docs.w3pk.org/recovery
|
|
64
|
+
Email: support@w3pk.org
|
|
65
|
+
|
|
66
|
+
Generated: ${new Date().toISOString()}
|
|
67
|
+
`}async restoreFromZipBackup(e,t){let r=JSON.parse(e);if(r.version!==1)throw new Error("Unsupported backup version");let{decryptWithPassword:n}=await Promise.resolve().then(()=>(S(),xe)),o=await n(r.encrypted,t,r.salt,r.iv,r.iterations),{Wallet:i}=await import("ethers"),c=i.fromPhrase(o);if(await b(c.address)!==r.metadata.addressChecksum)throw new Error("Address checksum mismatch - corrupted backup or wrong password");return{mnemonic:o,metadata:r.metadata}}}});var D,Be=u(()=>{"use strict";S();D=class{async createQRBackup(e,t,r={}){let n=r.errorCorrection||"H",o;if(r.password){let l=crypto.getRandomValues(new Uint8Array(32)),p=await B(e,r.password,l);o={version:1,type:"encrypted",data:p.encrypted,iv:p.iv,salt:p.salt,iterations:p.iterations,checksum:await b(t)}}else o={version:1,type:"plain",data:e,checksum:await b(t)};let i=JSON.stringify(o),c=await this.generateQRCode(i,n),a=this.getInstructions(t,!!r.password);return{qrCodeDataURL:c,rawData:i,instructions:a}}async generateQRCode(e,t){try{return await(await import("qrcode")).default.toDataURL(e,{errorCorrectionLevel:t,width:512,margin:2})}catch{return this.createFallbackQRDataURL(e)}}createFallbackQRDataURL(e){if(typeof document>"u")return`data:text/plain;base64,${Buffer.from(e).toString("base64")}`;let t=document.createElement("canvas");t.width=512,t.height=512;let r=t.getContext("2d");if(!r)throw new Error("Canvas not supported");r.fillStyle="white",r.fillRect(0,0,512,512),r.fillStyle="black",r.font="12px monospace",r.textAlign="center",r.textBaseline="middle";let n=this.wrapText(e,50),o=14,i=256-n.length*o/2;return n.forEach((c,a)=>{r.fillText(c,256,i+a*o)}),r.font="bold 16px sans-serif",r.fillText('Install "qrcode" package for QR codes',256,480),t.toDataURL("image/png")}wrapText(e,t){let r=[];for(let n=0;n<e.length;n+=t)r.push(e.substring(n,n+t));return r}getInstructions(e,t){return`
|
|
68
|
+
W3PK QR CODE BACKUP
|
|
69
|
+
==================
|
|
70
|
+
|
|
71
|
+
Ethereum Address: ${e}
|
|
72
|
+
Backup Date: ${new Date().toISOString()}
|
|
73
|
+
Type: ${t?"Encrypted":"Plain"}
|
|
74
|
+
|
|
75
|
+
STORAGE INSTRUCTIONS:
|
|
76
|
+
--------------------
|
|
77
|
+
|
|
78
|
+
\u{1F4F8} Print this QR code and store securely:
|
|
79
|
+
\u2713 Safe deposit box
|
|
80
|
+
\u2713 Home safe
|
|
81
|
+
\u2713 Trusted family member
|
|
82
|
+
\u2713 Multiple physical locations
|
|
83
|
+
|
|
84
|
+
\u26A0\uFE0F DO NOT:
|
|
85
|
+
\u2717 Store digitally (screenshot/photo)
|
|
86
|
+
\u2717 Upload to cloud
|
|
87
|
+
\u2717 Email or message
|
|
88
|
+
\u2717 Share publicly
|
|
89
|
+
|
|
90
|
+
RECOVERY STEPS:
|
|
91
|
+
--------------
|
|
92
|
+
|
|
93
|
+
1. Scan QR code with your phone camera or QR scanner app
|
|
94
|
+
|
|
95
|
+
2. Import the scanned data to w3pk recovery page
|
|
96
|
+
|
|
97
|
+
3. ${t?"Enter your backup password":"No password needed (plain backup)"}
|
|
98
|
+
|
|
99
|
+
4. Wallet will be restored
|
|
100
|
+
|
|
101
|
+
SECURITY NOTES:
|
|
102
|
+
--------------
|
|
103
|
+
|
|
104
|
+
${t?`
|
|
105
|
+
\u2713 This QR code is ENCRYPTED
|
|
106
|
+
\u2713 Password required to decrypt
|
|
107
|
+
\u2713 Safe to store in multiple locations
|
|
108
|
+
\u2713 Share with trusted family (they can't access without password)
|
|
109
|
+
`:`
|
|
110
|
+
\u26A0\uFE0F This QR code is NOT ENCRYPTED
|
|
111
|
+
\u26A0\uFE0F Anyone with access can recover your wallet
|
|
112
|
+
\u26A0\uFE0F Store with maximum security
|
|
113
|
+
\u26A0\uFE0F Do not photograph or copy
|
|
114
|
+
`}
|
|
115
|
+
|
|
116
|
+
ERROR CORRECTION:
|
|
117
|
+
----------------
|
|
118
|
+
|
|
119
|
+
This QR code has HIGH error correction (30% damage tolerance)
|
|
120
|
+
- Can survive partial damage
|
|
121
|
+
- Can handle folding/creasing
|
|
122
|
+
- Ensure good print quality
|
|
123
|
+
|
|
124
|
+
VERIFICATION:
|
|
125
|
+
------------
|
|
126
|
+
|
|
127
|
+
Address checksum is embedded in QR code
|
|
128
|
+
After scanning, verify address matches: ${e}
|
|
129
|
+
|
|
130
|
+
ALTERNATIVE RECOVERY:
|
|
131
|
+
-------------------
|
|
132
|
+
|
|
133
|
+
If QR code is lost/damaged, you can still recover using:
|
|
134
|
+
- Encrypted ZIP backup
|
|
135
|
+
- Social recovery
|
|
136
|
+
- Passkey sync
|
|
137
|
+
- Manual mnemonic entry (if you wrote down the words)
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
Generated by w3pk Recovery System
|
|
142
|
+
https://docs.w3pk.org/recovery
|
|
143
|
+
`}async restoreFromQR(e,t){let r=JSON.parse(e);if(r.version!==1)throw new Error("Unsupported QR backup version");let n;if(r.type==="encrypted"){if(!t)throw new Error("Password required for encrypted QR backup");let{decryptWithPassword:a}=await Promise.resolve().then(()=>(S(),xe));n=await a(r.data,t,r.salt,r.iv,r.iterations)}else if(r.type==="plain")n=r.data;else throw new Error("Unknown QR backup type");let{Wallet:o}=await import("ethers"),i=o.fromPhrase(n);if(await b(i.address)!==r.checksum)throw new Error("Address checksum mismatch - corrupted QR or wrong password");return{mnemonic:n,ethereumAddress:i.address}}}});var L,Xe=u(()=>{"use strict";Ce();Ie();Be();L=class{constructor(){this.storage=new x,this.zipCreator=new T,this.qrCreator=new D}async getBackupStatus(e){let t=await this.storage.getBackupsByAddress(e),r=await this.detectPasskeySync(),n=t.map(i=>({id:i.id,method:i.method,location:"local",createdAt:i.createdAt,deviceFingerprint:i.deviceFingerprint}));return{passkeySync:r,recoveryPhrase:{verified:!1,verificationCount:0,encryptedBackups:n},securityScore:this.calculateSecurityScore(r,n)}}async detectPasskeySync(){if(typeof navigator>"u")return{enabled:!1,deviceCount:0,lastSyncTime:void 0,platform:"unknown"};let e=navigator.userAgent.toLowerCase(),t="unknown";e.includes("mac")||e.includes("iphone")||e.includes("ipad")?t="apple":e.includes("android")?t="google":e.includes("windows")&&(t="microsoft");let r=await this.checkResidentKeySupport();return{enabled:r,deviceCount:1,lastSyncTime:r?Date.now():void 0,platform:t}}async checkResidentKeySupport(){if(typeof window>"u"||!window.PublicKeyCredential)return!1;try{return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()}catch{return!1}}calculateSecurityScore(e,t){let r={passkeyActive:e.enabled?20:0,passkeyMultiDevice:e.deviceCount>1?10:0,phraseVerified:0,encryptedBackup:t.length>0?20:0,socialRecovery:0},n=Object.values(r).reduce((c,a)=>c+a,0),o,i;return n<=20?(o="vulnerable",i='Create encrypted backup to reach "protected" (40+ pts)'):n<=50?(o="protected",i='Set up social recovery to reach "secured" (70+ pts)'):n<=80?(o="secured",i='Enable all methods to reach "fort-knox" (100 pts)'):(o="fort-knox",i="Maximum security achieved! \u{1F3C6}"),{total:n,breakdown:r,level:o,nextMilestone:i}}async createZipBackup(e,t,r){let{blob:n,metadata:o}=await this.zipCreator.createZipBackup(e,t,r);return await this.storage.storeBackupMetadata(o),n}async createQRBackup(e,t,r){let{qrCodeDataURL:n,rawData:o,instructions:i}=await this.qrCreator.createQRBackup(e,t,r),c={id:crypto.randomUUID(),ethereumAddress:t,method:"qr",createdAt:Date.now(),addressChecksum:JSON.parse(o).checksum};return await this.storage.storeBackupMetadata(c),{qrCodeDataURL:n,instructions:i}}async restoreFromZipBackup(e,t){let{mnemonic:r,metadata:n}=await this.zipCreator.restoreFromZipBackup(e,t);return{mnemonic:r,ethereumAddress:n.ethereumAddress}}async restoreFromQR(e,t){return await this.qrCreator.restoreFromQR(e,t)}async simulateRecoveryScenario(e,t){let r=[];switch(e.type){case"lost-device":t.passkeySync.enabled&&t.passkeySync.deviceCount>1&&r.push({method:"Passkey Sync (iCloud/Google)",success:!0,time:"5 minutes",requirements:["Sign in to cloud account","Authenticate on new device"]}),t.recoveryPhrase.encryptedBackups.length>0&&r.push({method:"Encrypted ZIP Backup",success:!0,time:"2 minutes",requirements:["Backup file","Password"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`]});break;case"lost-phrase":t.passkeySync.enabled&&r.push({method:"Passkey (current device)",success:!0,time:"instant",requirements:["Current device","Biometric/PIN"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`]});break;case"lost-both":t.passkeySync.enabled&&t.passkeySync.deviceCount>1&&r.push({method:"Passkey Sync",success:!0,time:"5 minutes",requirements:["Cloud account access","New device"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`]});break;case"switch-platform":t.recoveryPhrase.encryptedBackups.length>0&&r.push({method:"Encrypted Backup",success:!0,time:"2 minutes",requirements:["Backup file","Password"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`]});break}let n=r.length>0&&r.some(i=>i.success),o="";return n?(o=`\u2705 You're safe! ${r.length} way${r.length>1?"s":""} to recover.
|
|
144
|
+
|
|
145
|
+
`,o+=`Available recovery methods:
|
|
146
|
+
`,r.forEach(i=>{o+=`- ${i.method} (~${i.time})
|
|
147
|
+
`})):(o=`\u274C Wallet cannot be recovered in this scenario.
|
|
148
|
+
|
|
149
|
+
`,o+="Recommendation: Set up at least 2 backup methods to prevent total loss."),{scenario:e,success:n,availableMethods:r,timeEstimate:n?r[0].time:"N/A",educationalNote:o}}}});var et=u(()=>{"use strict"});var F={};A(F,{BackupManager:()=>L,BackupStorage:()=>x,QRBackupCreator:()=>D,ZipBackupCreator:()=>T,decryptWithPassword:()=>Ze,deriveAddressChecksum:()=>b,deriveKeyFromPassword:()=>ae,encryptWithPassword:()=>B,getDeviceFingerprint:()=>f,validatePasswordStrength:()=>ce});var I=u(()=>{"use strict";Xe();Ce();Ie();Be();et();S()});function Gt(){let s=new Uint8Array(1);return crypto.getRandomValues(s),s[0]}function Te(s,e,t){if(e>t)throw new Error("Threshold cannot be greater than total shares");if(e<2)throw new Error("Threshold must be at least 2");if(t>255)throw new Error("Cannot create more than 255 shares");let r=[];for(let n=0;n<t;n++)r[n]=new Uint8Array(s.length+1),r[n][0]=n+1;for(let n=0;n<s.length;n++){let i=[s[n]];for(let c=1;c<e;c++)i.push(Gt());for(let c=0;c<t;c++){let a=c+1,l=de.evaluatePolynomial(i,a);r[c][n+1]=l}}return r}function De(s,e){if(s.length<e)throw new Error(`Need at least ${e} shares to recover secret, got ${s.length}`);let t=s.slice(0,e),r=t[0].length;for(let i of t)if(i.length!==r)throw new Error("All shares must have the same length");let n=r-1,o=new Uint8Array(n);for(let i=0;i<n;i++){let c=[];for(let a of t)c.push({x:a[0],y:a[i+1]});o[i]=de.interpolate(c)}return o}function Fe(s){return new TextEncoder().encode(s)}function Me(s){return new TextDecoder().decode(s)}function Ke(s){return Array.from(s).map(e=>e.toString(16).padStart(2,"0")).join("")}function Oe(s){let e=new Uint8Array(s.length/2);for(let t=0;t<s.length;t+=2)e[t/2]=parseInt(s.substring(t,t+2),16);return e}var k,de,Ue=u(()=>{"use strict";k=class k{static multiply(e,t){return e===0||t===0?0:this.EXP_TABLE[(this.LOG_TABLE[e]+this.LOG_TABLE[t])%255]}static divide(e,t){if(t===0)throw new Error("Division by zero in GF(256)");return e===0?0:this.EXP_TABLE[(this.LOG_TABLE[e]-this.LOG_TABLE[t]+255)%255]}static add(e,t){return e^t}static evaluatePolynomial(e,t){let r=0;for(let n=e.length-1;n>=0;n--)r=this.add(this.multiply(r,t),e[n]);return r}static interpolate(e){let t=0;for(let r=0;r<e.length;r++){let n=e[r].y,o=1;for(let i=0;i<e.length;i++)r!==i&&(n=this.multiply(n,e[i].x),o=this.multiply(o,this.add(e[r].x,e[i].x)));t=this.add(t,this.divide(n,o))}return t}};k.LOG_TABLE=[],k.EXP_TABLE=[],(()=>{let e=(r,n)=>{let o=0;for(let i=0;i<8;i++){n&1&&(o^=r);let c=r&128;r<<=1,c&&(r^=283),n>>=1}return o&255},t=1;for(let r=0;r<255;r++)k.EXP_TABLE[r]=t,k.LOG_TABLE[t]=r,t=e(t,3);k.EXP_TABLE[255]=k.EXP_TABLE[0]})();de=k});var tt,q,rt=u(()=>{"use strict";Ue();tt=new Map,q=class{constructor(){this.storageKey="w3pk_social_recovery"}getItem(e){return typeof localStorage<"u"?localStorage.getItem(e):tt.get(e)||null}setItem(e,t){typeof localStorage<"u"?localStorage.setItem(e,t):tt.set(e,t)}async setupSocialRecovery(e,t,r,n){if(n>r.length)throw new Error("Threshold cannot be greater than number of guardians");if(n<2)throw new Error("Threshold must be at least 2");if(r.length>255)throw new Error("Cannot have more than 255 guardians");let o=Fe(e),i=Te(o,n,r.length),c=r.map((l,p)=>({id:crypto.randomUUID(),name:l.name,email:l.email,phone:l.phone,shareEncrypted:Ke(i[p]),status:"pending",addedAt:Date.now()})),a={threshold:n,totalGuardians:r.length,guardians:c,createdAt:Date.now(),ethereumAddress:t};return this.setItem(this.storageKey,JSON.stringify(a)),c}getSocialRecoveryConfig(){let e=this.getItem(this.storageKey);if(!e)return null;try{return JSON.parse(e)}catch{return null}}async generateGuardianInvite(e){let t=this.getSocialRecoveryConfig();if(!t)throw new Error("Social recovery not configured");let r=t.guardians.findIndex(a=>a.id===e.id);if(r===-1)throw new Error("Guardian not found");let n={version:1,guardianId:e.id,guardianName:e.name,guardianIndex:r+1,totalGuardians:t.totalGuardians,threshold:t.threshold,share:e.shareEncrypted,ethereumAddress:t.ethereumAddress,createdAt:t.createdAt},o=JSON.stringify(n),i=await this.generateQRCode(o),c=this.getGuardianExplainer(e.name,r+1,t.totalGuardians,t.threshold);return{guardianId:e.id,qrCode:i,shareCode:o,explainer:c}}async generateQRCode(e){try{return await(await import("qrcode")).toDataURL(e,{errorCorrectionLevel:"H",width:512,margin:2})}catch{return this.createPlaceholderQR(e)}}createPlaceholderQR(e){if(typeof document>"u")return`data:text/plain;base64,${Buffer.from(e).toString("base64")}`;let t=document.createElement("canvas");t.width=512,t.height=512;let r=t.getContext("2d");return r?(r.fillStyle="white",r.fillRect(0,0,512,512),r.fillStyle="black",r.font="bold 20px sans-serif",r.textAlign="center",r.textBaseline="middle",r.fillText("Guardian Recovery Share",256,100),r.font="14px monospace",r.fillText('Install "qrcode" for QR codes',256,480),r.font="10px monospace",this.wrapText(e.substring(0,200)+"...",60).forEach((o,i)=>{r.fillText(o,256,150+i*12)}),t.toDataURL("image/png")):""}wrapText(e,t){let r=[];for(let n=0;n<e.length;n+=t)r.push(e.substring(n,n+t));return r}getGuardianExplainer(e,t,r,n){return`
|
|
150
|
+
\u{1F6E1}\uFE0F GUARDIAN RECOVERY SHARE
|
|
151
|
+
|
|
152
|
+
Dear ${e},
|
|
153
|
+
|
|
154
|
+
You have been chosen as Guardian ${t} of ${r}
|
|
155
|
+
|
|
156
|
+
YOUR ROLE:
|
|
157
|
+
----------
|
|
158
|
+
|
|
159
|
+
Your friend has entrusted you with a recovery share for their cryptocurrency wallet.
|
|
160
|
+
|
|
161
|
+
WHAT THIS MEANS:
|
|
162
|
+
- You hold 1 piece of a ${n}-piece puzzle
|
|
163
|
+
- ${n} guardians needed to recover the wallet
|
|
164
|
+
- You cannot access the wallet alone
|
|
165
|
+
- This is a responsibility and honor
|
|
166
|
+
|
|
167
|
+
HOW IT WORKS:
|
|
168
|
+
-------------
|
|
169
|
+
|
|
170
|
+
If your friend loses access to their wallet:
|
|
171
|
+
|
|
172
|
+
1. They will contact you to request your share
|
|
173
|
+
2. You provide the QR code or share code below
|
|
174
|
+
3. System collects shares from ${n} guardians
|
|
175
|
+
4. Wallet is mathematically reconstructed
|
|
176
|
+
5. Your friend regains access \u2713
|
|
177
|
+
|
|
178
|
+
SECURITY:
|
|
179
|
+
---------
|
|
180
|
+
|
|
181
|
+
\u2713 Your share is encrypted
|
|
182
|
+
\u2713 Cannot be used alone
|
|
183
|
+
\u2713 ${n-1} other guardians needed
|
|
184
|
+
\u2713 Safe to store digitally
|
|
185
|
+
|
|
186
|
+
YOUR RESPONSIBILITIES:
|
|
187
|
+
---------------------
|
|
188
|
+
|
|
189
|
+
DO:
|
|
190
|
+
\u2713 Keep this share safe and accessible
|
|
191
|
+
\u2713 Store in password manager or secure location
|
|
192
|
+
\u2713 Respond promptly if friend requests recovery
|
|
193
|
+
\u2713 Verify identity before sharing
|
|
194
|
+
|
|
195
|
+
DON'T:
|
|
196
|
+
\u2717 Share unless friend explicitly requests it
|
|
197
|
+
\u2717 Post publicly or send unsecured
|
|
198
|
+
\u2717 Lose or delete (friend depends on you!)
|
|
199
|
+
|
|
200
|
+
VERIFICATION:
|
|
201
|
+
------------
|
|
202
|
+
|
|
203
|
+
Before sharing, verify your friend's identity:
|
|
204
|
+
- Video call to confirm
|
|
205
|
+
- Ask security questions
|
|
206
|
+
- Check contact method is authentic
|
|
207
|
+
|
|
208
|
+
RECOVERY PROCESS:
|
|
209
|
+
----------------
|
|
210
|
+
|
|
211
|
+
If requested:
|
|
212
|
+
1. Friend will contact you
|
|
213
|
+
2. Verify their identity
|
|
214
|
+
3. Provide this QR code or share code
|
|
215
|
+
4. System handles the rest
|
|
216
|
+
|
|
217
|
+
HOW TO STORE:
|
|
218
|
+
------------
|
|
219
|
+
|
|
220
|
+
Recommended storage:
|
|
221
|
+
\u2713 Password manager (1Password, Bitwarden)
|
|
222
|
+
\u2713 Encrypted note
|
|
223
|
+
\u2713 Print and store physically
|
|
224
|
+
\u2713 Screenshot (encrypted phone)
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
Guardian ${t}/${r} | Threshold: ${n}/${r}
|
|
229
|
+
Created: ${new Date().toISOString()}
|
|
230
|
+
|
|
231
|
+
Thank you for being a trusted guardian!
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
NEED HELP?
|
|
236
|
+
Visit: https://docs.w3pk.org/social-recovery
|
|
237
|
+
`}async recoverFromGuardians(e){let t=this.getSocialRecoveryConfig();if(!t)throw new Error("Social recovery not configured");if(e.length<t.threshold)throw new Error(`Need at least ${t.threshold} shares, got ${e.length}`);let n=e.map(l=>{let p=JSON.parse(l);return{guardianId:p.guardianId,share:p.share,index:p.guardianIndex}}).map(l=>Oe(l.share)),o=De(n,t.threshold),i=Me(o),{Wallet:c}=await import("ethers"),a=c.fromPhrase(i);if(a.address.toLowerCase()!==t.ethereumAddress.toLowerCase())throw new Error("Recovered address does not match - invalid shares");return{mnemonic:i,ethereumAddress:a.address}}getRecoveryProgress(e){let t=this.getSocialRecoveryConfig();if(!t)throw new Error("Social recovery not configured");let r=new Set(e.map(n=>{try{return JSON.parse(n).guardianId}catch{return null}}).filter(Boolean));return{collected:r.size,required:t.threshold,guardians:t.guardians.map(n=>({id:n.id,name:n.name,hasProvided:r.has(n.id)})),canRecover:r.size>=t.threshold}}markGuardianVerified(e){let t=this.getSocialRecoveryConfig();if(!t)throw new Error("Social recovery not configured");let r=t.guardians.find(n=>n.id===e);if(!r)throw new Error("Guardian not found");r.status="active",r.lastVerified=Date.now(),this.setItem(this.storageKey,JSON.stringify(t))}revokeGuardian(e){let t=this.getSocialRecoveryConfig();if(!t)throw new Error("Social recovery not configured");let r=t.guardians.find(n=>n.id===e);if(!r)throw new Error("Guardian not found");r.status="revoked",this.setItem(this.storageKey,JSON.stringify(t))}async addGuardian(e,t){let r=this.getSocialRecoveryConfig();if(!r)throw new Error("Social recovery not configured");let n=[...r.guardians.filter(i=>i.status!=="revoked").map(i=>({name:i.name,email:i.email,phone:i.phone})),t],o=await this.setupSocialRecovery(e,r.ethereumAddress,n,r.threshold);return o[o.length-1]}}});var nt=u(()=>{"use strict"});var le={};A(le,{SocialRecoveryManager:()=>q,bytesToHex:()=>Ke,bytesToString:()=>Me,combineShares:()=>De,hexToBytes:()=>Oe,splitSecret:()=>Te,stringToBytes:()=>Fe});var G=u(()=>{"use strict";rt();Ue();nt()});var $,st=u(()=>{"use strict";S();$=class{async createSyncPackage(e,t,r){let n=await f(),{deriveEncryptionKeyFromWebAuthn:o,encryptData:i}=await Promise.resolve().then(()=>(ne(),Se)),c=await o(t,r),a=await i(e,c),l=this.detectSyncMethod();return{id:crypto.randomUUID(),encryptedData:a,deviceFingerprints:[n],syncMethod:l,version:1,updatedAt:Date.now()}}detectSyncMethod(){let e=navigator.userAgent.toLowerCase();return e.includes("mac")||e.includes("iphone")||e.includes("ipad")?"icloud":e.includes("android")||e.includes("chrome")?"google":e.includes("windows")?"microsoft":"custom"}async restoreFromSync(e,t,r){let{deriveEncryptionKeyFromWebAuthn:n,decryptData:o}=await Promise.resolve().then(()=>(ne(),Se)),i=await n(t,r),c=await o(e.encryptedData,i),a=await f();return e.deviceFingerprints.includes(a)||(e.deviceFingerprints.push(a),e.updatedAt=Date.now()),c}async getSetupFlow(){return{success:!1,steps:[{title:"1. Authenticate on New Device",action:"Use Touch ID/Face ID",educational:"Your passkey is automatically synced via iCloud/Google",status:"waiting"},{title:"2. Decrypt Wallet Data",action:"System validates device",educational:"Only your trusted devices can decrypt the wallet",status:"waiting"},{title:"3. Verify Recovery",action:"Check partial address",educational:"Confirm address matches your wallet",status:"waiting"},{title:"4. Ready!",action:"Wallet synced",educational:"All devices now have access",status:"waiting"}]}}getSyncExplainer(){return`
|
|
238
|
+
HOW CROSS-DEVICE SYNC WORKS
|
|
239
|
+
===========================
|
|
240
|
+
|
|
241
|
+
Your wallet is protected by TWO layers:
|
|
242
|
+
|
|
243
|
+
Layer 1: Passkey (Auto-Syncs)
|
|
244
|
+
-----------------------------
|
|
245
|
+
\u2713 Your fingerprint/Face ID credential
|
|
246
|
+
\u2713 Syncs via iCloud Keychain or Google
|
|
247
|
+
\u2713 Available on all your devices
|
|
248
|
+
\u2713 Cannot be stolen (hardware-protected)
|
|
249
|
+
|
|
250
|
+
Layer 2: Encrypted Wallet (In Browser)
|
|
251
|
+
--------------------------------------
|
|
252
|
+
\u2713 Your 12-word mnemonic (encrypted)
|
|
253
|
+
\u2713 Stored in browser's IndexedDB
|
|
254
|
+
\u2713 Can ONLY be decrypted with passkey
|
|
255
|
+
\u2713 Requires authentication to access
|
|
256
|
+
|
|
257
|
+
How They Work Together:
|
|
258
|
+
----------------------
|
|
259
|
+
|
|
260
|
+
Device 1 (iPhone) iCloud Keychain Device 2 (Mac)
|
|
261
|
+
| | |
|
|
262
|
+
|-- Passkey Created -------->| |
|
|
263
|
+
| | |
|
|
264
|
+
|-- Wallet Encrypted --------| |
|
|
265
|
+
| (in IndexedDB) | |
|
|
266
|
+
| | |
|
|
267
|
+
| |<----- Login on Mac -----|
|
|
268
|
+
| | |
|
|
269
|
+
| Passkey Synced |
|
|
270
|
+
| |------- Unlock --------->|
|
|
271
|
+
| | Passkey |
|
|
272
|
+
| | |
|
|
273
|
+
| |<----- Decrypt ----------|
|
|
274
|
+
| | Wallet |
|
|
275
|
+
|
|
276
|
+
Security Benefits:
|
|
277
|
+
-----------------
|
|
278
|
+
|
|
279
|
+
\u2713 Passkey syncs automatically (convenient)
|
|
280
|
+
\u2713 Wallet ONLY decrypts with passkey (secure)
|
|
281
|
+
\u2713 Cannot access wallet without biometric
|
|
282
|
+
\u2713 Works seamlessly across devices
|
|
283
|
+
\u2713 No passwords to remember
|
|
284
|
+
|
|
285
|
+
What Gets Synced:
|
|
286
|
+
----------------
|
|
287
|
+
|
|
288
|
+
\u2713 Passkey credential (via platform sync)
|
|
289
|
+
\u2713 Credential metadata (localStorage)
|
|
290
|
+
|
|
291
|
+
What Stays Local:
|
|
292
|
+
----------------
|
|
293
|
+
|
|
294
|
+
\u2717 Encrypted mnemonic (in IndexedDB)
|
|
295
|
+
- Must authenticate to decrypt
|
|
296
|
+
- Requires passkey access
|
|
297
|
+
- Platform-specific storage
|
|
298
|
+
|
|
299
|
+
Recovery Options:
|
|
300
|
+
----------------
|
|
301
|
+
|
|
302
|
+
If you lose a device:
|
|
303
|
+
1. \u2713 Sign in on new device
|
|
304
|
+
2. \u2713 Passkey auto-syncs
|
|
305
|
+
3. \u2713 Authenticate to decrypt wallet
|
|
306
|
+
4. \u2713 Wallet restored!
|
|
307
|
+
|
|
308
|
+
If passkey doesn't sync:
|
|
309
|
+
1. \u2713 Use encrypted backup
|
|
310
|
+
2. \u2713 Use 12-word recovery phrase
|
|
311
|
+
3. \u2713 Use social recovery
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
Think of it like:
|
|
316
|
+
\u{1F511} Passkey = Your car key (syncs via keychain)
|
|
317
|
+
\u{1F697} Wallet = Your car (locked, needs key to start)
|
|
318
|
+
`}}});var Y,ot=u(()=>{"use strict";S();Y=class{constructor(){this.storageKey="w3pk_devices"}async registerDevice(){let e={id:await f(),name:this.getDeviceName(),platform:this.detectPlatform(),lastActive:Date.now(),trusted:!0,canRevoke:!1},t=await this.getDevices(),r=t.find(n=>n.id===e.id);return r?(r.lastActive=Date.now(),localStorage.setItem(this.storageKey,JSON.stringify(t))):(t.push(e),localStorage.setItem(this.storageKey,JSON.stringify(t))),e}async getDevices(){let e=localStorage.getItem(this.storageKey);if(!e)return[];try{return JSON.parse(e)}catch{return[]}}async getSyncStatus(){let e=await this.getDevices(),t=await f();e.forEach(o=>{o.canRevoke=o.id!==t}),e.sort((o,i)=>i.lastActive-o.lastActive);let r=this.detectPlatform(),n=e.length>1?Math.max(...e.map(o=>o.lastActive)):void 0;return{enabled:e.length>1,devices:e,lastSyncTime:n,platform:this.getPlatformName(r)}}async revokeDevice(e){let t=await this.getDevices(),r=await f();if(e===r)throw new Error("Cannot revoke current device");let n=t.filter(o=>o.id!==e);localStorage.setItem(this.storageKey,JSON.stringify(n))}async updateLastActive(){let e=await this.getDevices(),t=await f(),r=e.find(n=>n.id===t);r&&(r.lastActive=Date.now(),localStorage.setItem(this.storageKey,JSON.stringify(e)))}getDeviceName(){let e=this.detectPlatform(),t=navigator.userAgent;if(e==="ios")return t.includes("iPhone")?"iPhone":t.includes("iPad")?"iPad":t.includes("iPod")?"iPod":"iOS Device";if(e==="android"){let r=t.match(/Android.*;\s([^)]+)\)/);return r?r[1]:"Android Device"}return e==="macos"?"Mac":e==="windows"?"Windows PC":e==="linux"?"Linux PC":"Unknown Device"}detectPlatform(){let e=navigator.userAgent.toLowerCase();return e.includes("iphone")||e.includes("ipad")||e.includes("ipod")?"ios":e.includes("android")?"android":e.includes("mac")?"macos":e.includes("windows")?"windows":e.includes("linux")?"linux":"unknown"}getPlatformName(e){switch(e){case"ios":return"iOS (iCloud Keychain)";case"android":return"Android (Google)";case"macos":return"macOS (iCloud Keychain)";case"windows":return"Windows (Microsoft)";case"linux":return"Linux";default:return"Unknown"}}async getDeviceListFormatted(){let e=await this.getSyncStatus();if(e.devices.length===0)return"No devices registered";let t=`Your Devices (${e.devices.length}):
|
|
319
|
+
|
|
320
|
+
`;return e.devices.forEach((r,n)=>{let o=Date.now()-r.lastActive,i=this.formatTimeDiff(o);t+=`${n+1}. ${r.name}
|
|
321
|
+
`,t+=` Platform: ${this.getPlatformName(r.platform)}
|
|
322
|
+
`,t+=` Last active: ${i}
|
|
323
|
+
`,t+=` ${r.canRevoke?"":"(Current device)"}
|
|
324
|
+
|
|
325
|
+
`}),t}formatTimeDiff(e){let t=Math.floor(e/1e3),r=Math.floor(t/60),n=Math.floor(r/60),o=Math.floor(n/24);return o>0?`${o} day${o>1?"s":""} ago`:n>0?`${n} hour${n>1?"s":""} ago`:r>0?`${r} minute${r>1?"s":""} ago`:"just now"}}});var H,it=u(()=>{"use strict";H=class{async detectSyncCapabilities(){let e=this.detectPlatform(),t=await this.checkPasskeySync(),r=this.estimateDeviceCount();return{passkeysSync:t,platform:e,estimatedDevices:r,syncEnabled:t&&e!=="none"}}detectPlatform(){let e=navigator.userAgent.toLowerCase();return e.includes("mac")||e.includes("iphone")||e.includes("ipad")||e.includes("ipod")?"apple":e.includes("android")?"google":e.includes("windows")?"microsoft":"none"}async checkPasskeySync(){if(!window.PublicKeyCredential)return!1;try{return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()}catch{return!1}}estimateDeviceCount(){return 1}getPlatformEducation(e){switch(e){case"apple":return`
|
|
326
|
+
\u{1F34E} Apple iCloud Keychain
|
|
327
|
+
|
|
328
|
+
Your passkey automatically syncs across:
|
|
329
|
+
- iPhone
|
|
330
|
+
- iPad
|
|
331
|
+
- Mac
|
|
332
|
+
- Apple Watch (for authentication)
|
|
333
|
+
|
|
334
|
+
Requirements:
|
|
335
|
+
\u2713 iCloud Keychain enabled in Settings
|
|
336
|
+
\u2713 Signed in to iCloud
|
|
337
|
+
\u2713 Two-factor authentication enabled
|
|
338
|
+
|
|
339
|
+
How it works:
|
|
340
|
+
1. Create passkey on this device
|
|
341
|
+
2. iCloud encrypts and syncs it
|
|
342
|
+
3. Available on all your Apple devices
|
|
343
|
+
4. End-to-end encrypted
|
|
344
|
+
|
|
345
|
+
Security:
|
|
346
|
+
- Apple cannot access your passkeys
|
|
347
|
+
- Synced with end-to-end encryption
|
|
348
|
+
- Requires device unlock to use
|
|
349
|
+
`;case"google":return`
|
|
350
|
+
\u{1F916} Google Password Manager
|
|
351
|
+
|
|
352
|
+
Your passkey automatically syncs across:
|
|
353
|
+
- Android phones/tablets
|
|
354
|
+
- Chrome browser (all platforms)
|
|
355
|
+
- ChromeOS devices
|
|
356
|
+
|
|
357
|
+
Requirements:
|
|
358
|
+
\u2713 Signed in to Google Account
|
|
359
|
+
\u2713 Sync enabled in Chrome
|
|
360
|
+
\u2713 Screen lock configured
|
|
361
|
+
|
|
362
|
+
How it works:
|
|
363
|
+
1. Create passkey on this device
|
|
364
|
+
2. Google encrypts and syncs it
|
|
365
|
+
3. Available on all signed-in devices
|
|
366
|
+
4. End-to-end encrypted
|
|
367
|
+
|
|
368
|
+
Security:
|
|
369
|
+
- Google cannot access your passkeys
|
|
370
|
+
- Synced with end-to-end encryption
|
|
371
|
+
- Requires screen unlock to use
|
|
372
|
+
`;case"microsoft":return`
|
|
373
|
+
\u{1FA9F} Windows Hello
|
|
374
|
+
|
|
375
|
+
Your passkey is tied to this Windows device.
|
|
376
|
+
|
|
377
|
+
Note: Limited cross-device sync
|
|
378
|
+
- Passkeys stored in Windows Credential Manager
|
|
379
|
+
- Tied to this PC's TPM chip
|
|
380
|
+
- Does NOT sync to other devices automatically
|
|
381
|
+
|
|
382
|
+
For cross-device access:
|
|
383
|
+
- Use encrypted backup instead
|
|
384
|
+
- Or set up social recovery
|
|
385
|
+
|
|
386
|
+
Security:
|
|
387
|
+
- Hardware-protected (TPM)
|
|
388
|
+
- Requires Windows Hello (PIN/biometric)
|
|
389
|
+
- Very secure but not portable
|
|
390
|
+
`;case"none":return`
|
|
391
|
+
\u26A0\uFE0F Platform Sync Not Available
|
|
392
|
+
|
|
393
|
+
Your passkey will be stored on this device only.
|
|
394
|
+
|
|
395
|
+
Recommendation:
|
|
396
|
+
\u2713 Create encrypted backup (password-protected)
|
|
397
|
+
\u2713 Set up social recovery
|
|
398
|
+
\u2713 Save your 12-word recovery phrase
|
|
399
|
+
|
|
400
|
+
This ensures you can recover if:
|
|
401
|
+
- Device is lost/stolen
|
|
402
|
+
- Device is damaged
|
|
403
|
+
- You switch devices
|
|
404
|
+
`}}getSyncInstructions(e){switch(e){case"apple":return["Open Settings on your iPhone/iPad/Mac","Tap your name at the top",'Tap "iCloud"','Enable "Keychain"','Enable "iCloud Backup" (recommended)'];case"google":return["Open Chrome Settings",'Click "You and Google"','Enable "Sync"','Ensure "Passwords" is checked',"Sign in on other devices with same Google account"];case"microsoft":return["Windows Hello sync is limited","Consider using:","- Encrypted ZIP backup","- Social recovery","- Cloud backup (password-protected)"];case"none":return["Platform sync not available","Use alternative backup methods:","- Create encrypted ZIP backup","- Set up social recovery","- Save recovery phrase securely"]}}}});var at=u(()=>{"use strict"});var We={};A(We,{DeviceManager:()=>Y,PlatformDetector:()=>H,VaultSync:()=>$});var ue=u(()=>{"use strict";st();ot();it();at()});var _,ct=u(()=>{"use strict";_=class{getScenarios(){return[{type:"lost-device",description:"Your phone fell in the ocean"},{type:"lost-phrase",description:"Your paper backup burned in a fire"},{type:"lost-both",description:"Phone stolen AND forgot recovery phrase"},{type:"switch-platform",description:"Switching from iPhone to Android"}]}async simulateScenario(e,t){let r=[];switch(e.type){case"lost-device":t.passkeySync.enabled&&t.passkeySync.deviceCount>1&&r.push({method:`Passkey Sync (${t.passkeySync.platform})`,success:!0,time:"5 minutes",requirements:["Sign in to cloud account on new device","Authenticate with biometric/PIN"]}),t.recoveryPhrase.encryptedBackups.length>0&&t.recoveryPhrase.encryptedBackups.forEach(i=>{r.push({method:`Encrypted ${i.method.toUpperCase()} Backup`,success:!0,time:"2 minutes",requirements:["Backup file/QR code","Backup password"]})}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`Contact ${t.socialRecovery.threshold} guardians`,"Collect shares from guardians","Verify identity with each guardian"]});break;case"lost-phrase":t.passkeySync.enabled&&r.push({method:"Passkey (current device)",success:!0,time:"Instant",requirements:["Access to current device","Biometric/PIN authentication"]}),t.passkeySync.deviceCount>1&&r.push({method:"Passkey Sync",success:!0,time:"5 minutes",requirements:["Any synced device","Biometric authentication"]}),t.recoveryPhrase.encryptedBackups.length>0&&r.push({method:"Encrypted Backup",success:!0,time:"2 minutes",requirements:["Backup file","Password"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`]});break;case"lost-both":t.passkeySync.deviceCount>1&&r.push({method:"Passkey Sync",success:!0,time:"5 minutes",requirements:["Cloud account access","New device","Biometric setup"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`,"Identity verification with guardians"]});break;case"switch-platform":t.recoveryPhrase.encryptedBackups.length>0&&r.push({method:"Encrypted Backup",success:!0,time:"2 minutes",requirements:["Backup file","Password"]}),t.socialRecovery?.enabled&&r.push({method:"Social Recovery",success:!0,time:"24 hours",requirements:[`${t.socialRecovery.threshold} guardian shares`]});break}let n=r.length>0,o=this.getEducationalNote(e,r,t);return{scenario:e,success:n,availableMethods:r,timeEstimate:n?this.estimateFastestRecovery(r):"Cannot recover",educationalNote:o}}estimateFastestRecovery(e){let t=e.map(r=>r.time.toLowerCase());if(t.some(r=>r.includes("instant")))return"Instant";if(t.some(r=>r.includes("minute"))){let r=t.filter(n=>n.includes("minute")).map(n=>parseInt(n));return`${Math.min(...r)} minutes`}return t.some(r=>r.includes("hour"))?"24 hours":"Unknown"}getEducationalNote(e,t,r){if(t.length===0)return`
|
|
405
|
+
\u274C WALLET CANNOT BE RECOVERED
|
|
406
|
+
|
|
407
|
+
Scenario: ${e.description}
|
|
408
|
+
|
|
409
|
+
Unfortunately, you have no recovery options for this scenario.
|
|
410
|
+
|
|
411
|
+
This is why backup is critical!
|
|
412
|
+
|
|
413
|
+
IMMEDIATE ACTION REQUIRED:
|
|
414
|
+
-------------------------
|
|
415
|
+
|
|
416
|
+
To prevent permanent loss, set up AT LEAST TWO of these:
|
|
417
|
+
|
|
418
|
+
1. Encrypted Backup
|
|
419
|
+
- Takes 2 minutes
|
|
420
|
+
- Works anywhere
|
|
421
|
+
- Requires password
|
|
422
|
+
[Create backup now]
|
|
423
|
+
|
|
424
|
+
2. Social Recovery
|
|
425
|
+
- Takes 30 minutes setup
|
|
426
|
+
- Most secure
|
|
427
|
+
- Requires 3-5 trusted friends
|
|
428
|
+
[Set up guardians]
|
|
429
|
+
|
|
430
|
+
3. Verify Passkey Sync
|
|
431
|
+
- Check if enabled
|
|
432
|
+
- Test on another device
|
|
433
|
+
- Platform-specific
|
|
434
|
+
[Check sync status]
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
Don't wait until it's too late!
|
|
439
|
+
`;let n=`\u2705 YOU CAN RECOVER!
|
|
440
|
+
|
|
441
|
+
Scenario: ${e.description}
|
|
442
|
+
|
|
443
|
+
`;return n+=`Available recovery methods (${t.length}):
|
|
444
|
+
|
|
445
|
+
`,t.forEach((o,i)=>{n+=`${i+1}. ${o.method}
|
|
446
|
+
`,n+=` \u23F1 Time: ~${o.time}
|
|
447
|
+
`,n+=` Requirements:
|
|
448
|
+
`,o.requirements.forEach(c=>{n+=` - ${c}
|
|
449
|
+
`}),n+=`
|
|
450
|
+
`}),n+=`
|
|
451
|
+
RECOMMENDATIONS:
|
|
452
|
+
`,t.length===1?(n+=`\u26A0\uFE0F You only have ONE recovery method.
|
|
453
|
+
`,n+=` Add more backups for better security:
|
|
454
|
+
`,r.recoveryPhrase.encryptedBackups.length||(n+=` - Create encrypted backup
|
|
455
|
+
`),r.socialRecovery?.enabled||(n+=` - Set up social recovery
|
|
456
|
+
`)):t.length===2?(n+=`\u{1F7E1} Good! You have ${t.length} recovery methods.
|
|
457
|
+
`,r.socialRecovery?.enabled||(n+=` Consider adding social recovery for maximum security.
|
|
458
|
+
`)):(n+=`\u{1F7E2} Excellent! You have ${t.length} recovery methods.
|
|
459
|
+
`,n+=` Your wallet is well-protected!
|
|
460
|
+
`),n}async runInteractiveTest(e){let t=this.getScenarios(),r=[];for(let c of t){let a=await this.simulateScenario(c,e);r.push(a)}let n=r.filter(c=>c.success).length,o=n/t.length*100,i="";return o===100?i=`\u{1F3C6} PERFECT SCORE!
|
|
461
|
+
|
|
462
|
+
You can recover in ALL scenarios.
|
|
463
|
+
Your wallet is extremely well-protected!`:o>=75?i=`\u{1F7E2} GREAT JOB!
|
|
464
|
+
|
|
465
|
+
You can recover in ${n}/${t.length} scenarios.
|
|
466
|
+
Consider adding more backup methods for complete coverage.`:o>=50?i=`\u{1F7E1} GOOD START!
|
|
467
|
+
|
|
468
|
+
You can recover in ${n}/${t.length} scenarios.
|
|
469
|
+
Add more backup methods to improve security.`:i=`\u26A0\uFE0F AT RISK!
|
|
470
|
+
|
|
471
|
+
You can only recover in ${n}/${t.length} scenarios.
|
|
472
|
+
Urgently add backup methods to protect your wallet!`,{scenarios:r,overallScore:o,feedback:i}}}});function dt(s){return pe[s]||null}function lt(){return Object.keys(pe)}function ut(s){let e=s.toLowerCase();return Object.values(pe).filter(t=>t.title.toLowerCase().includes(e)||t.content.toLowerCase().includes(e))}var pe,pt=u(()=>{"use strict";pe={whatIsPasskey:{title:"What is a Passkey?",content:`
|
|
473
|
+
Think of a passkey like your house smart lock:
|
|
474
|
+
|
|
475
|
+
\u{1F511} Traditional Key (Password):
|
|
476
|
+
- Can be stolen
|
|
477
|
+
- Can be forgotten
|
|
478
|
+
- Same key opens many doors
|
|
479
|
+
- Written on paper or in memory
|
|
480
|
+
|
|
481
|
+
\u{1F3E0} Smart Lock (Passkey):
|
|
482
|
+
- Biometric only (your face/finger)
|
|
483
|
+
- Can't be stolen or forgotten
|
|
484
|
+
- Unique per device
|
|
485
|
+
- Auto-syncs to your other devices
|
|
486
|
+
|
|
487
|
+
HOW IT WORKS:
|
|
488
|
+
1. You create a passkey with your fingerprint
|
|
489
|
+
2. Your device stores it securely (hardware-protected)
|
|
490
|
+
3. It syncs to your other devices automatically
|
|
491
|
+
4. Only YOU can use it (with your biometric)
|
|
492
|
+
|
|
493
|
+
SECURITY:
|
|
494
|
+
- The private key never leaves your device
|
|
495
|
+
- Cannot be phished (checks website domain)
|
|
496
|
+
- Cannot be guessed or brute-forced
|
|
497
|
+
- Requires physical access to your device + your biometric
|
|
498
|
+
`,interactive:"Try authenticating now \u2192"},whatIsRecoveryPhrase:{title:"What is a Recovery Phrase?",content:`
|
|
499
|
+
Your 12 words are like a master key to your cryptocurrency wallet.
|
|
500
|
+
|
|
501
|
+
\u{1F331} 12 Words = Your Entire Wallet:
|
|
502
|
+
- Generate unlimited accounts
|
|
503
|
+
- Sign transactions
|
|
504
|
+
- Prove ownership of funds
|
|
505
|
+
- Recover on ANY wallet app
|
|
506
|
+
|
|
507
|
+
HOW IT WORKS:
|
|
508
|
+
1. 12 random words from dictionary
|
|
509
|
+
2. Creates a "seed" using math
|
|
510
|
+
3. Seed generates your private keys
|
|
511
|
+
4. Private keys control your crypto
|
|
512
|
+
|
|
513
|
+
WHY 12 WORDS?
|
|
514
|
+
- 2^128 possible combinations
|
|
515
|
+
- Would take billions of years to guess
|
|
516
|
+
- Easy for humans to write down
|
|
517
|
+
- Works in any BIP39 wallet
|
|
518
|
+
|
|
519
|
+
CRITICAL SECURITY:
|
|
520
|
+
\u26A0\uFE0F Anyone with these words = OWNS your wallet
|
|
521
|
+
\u26A0\uFE0F Cannot be changed or reset
|
|
522
|
+
\u26A0\uFE0F Lost words = Lost wallet forever
|
|
523
|
+
\u26A0\uFE0F Store safely offline
|
|
524
|
+
|
|
525
|
+
BEST PRACTICES:
|
|
526
|
+
\u2713 Write on paper (offline)
|
|
527
|
+
\u2713 Store in safe/vault
|
|
528
|
+
\u2713 Never type in computer (except during setup)
|
|
529
|
+
\u2713 Never share with anyone
|
|
530
|
+
\u2713 Make encrypted backup (password-protected)
|
|
531
|
+
\u2713 Consider social recovery (split among friends)
|
|
532
|
+
`},howDoesSyncWork:{title:"How Does Passkey Sync Work?",content:`
|
|
533
|
+
Your passkey and wallet use TWO layers of protection:
|
|
534
|
+
|
|
535
|
+
LAYER 1: PASSKEY (Auto-Syncs)
|
|
536
|
+
------------------------------
|
|
537
|
+
Your passkey credential syncs via:
|
|
538
|
+
- iCloud Keychain (Apple)
|
|
539
|
+
- Google Password Manager (Android)
|
|
540
|
+
- Microsoft Account (Windows)
|
|
541
|
+
|
|
542
|
+
What syncs:
|
|
543
|
+
\u2713 Passkey credential
|
|
544
|
+
\u2713 Public key
|
|
545
|
+
\u2713 User info
|
|
546
|
+
|
|
547
|
+
What DOESN'T sync:
|
|
548
|
+
\u2717 Your 12-word mnemonic
|
|
549
|
+
\u2717 Private keys
|
|
550
|
+
\u2717 Wallet contents
|
|
551
|
+
|
|
552
|
+
LAYER 2: ENCRYPTED WALLET (Local)
|
|
553
|
+
----------------------------------
|
|
554
|
+
Your wallet is encrypted and stored in:
|
|
555
|
+
- Browser's IndexedDB
|
|
556
|
+
- Encrypted with passkey-derived key
|
|
557
|
+
- Can ONLY be decrypted with passkey
|
|
558
|
+
|
|
559
|
+
THE SYNC FLOW:
|
|
560
|
+
|
|
561
|
+
Device 1 (iPhone) \u2192 iCloud \u2192 Device 2 (Mac)
|
|
562
|
+
Passkey created Syncs Passkey available
|
|
563
|
+
Wallet encrypted ---- Decrypt with passkey
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
WHAT THIS MEANS:
|
|
567
|
+
- Passkey syncs = Convenient
|
|
568
|
+
- Wallet encrypted = Secure
|
|
569
|
+
- Need both = Protected
|
|
570
|
+
- Works across devices = Seamless
|
|
571
|
+
|
|
572
|
+
SECURITY:
|
|
573
|
+
- Even if iCloud is hacked, wallet stays encrypted
|
|
574
|
+
- Even if wallet is copied, can't decrypt without passkey
|
|
575
|
+
- Both layers needed to access funds
|
|
576
|
+
`,visual:`
|
|
577
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510
|
|
578
|
+
\u2502 DEVICE 1 DEVICE 2 \u2502
|
|
579
|
+
\u2502 \u2502
|
|
580
|
+
\u2502 \u{1F511} Passkey \u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500 \u{1F511} Passkey \u2502
|
|
581
|
+
\u2502 \u2502 \u2502 \u2502
|
|
582
|
+
\u2502 \u{1F512} Wallet \u25C4\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u2500\u253C\u2500\u2500\u2500\u25BA \u{1F512} Wallet \u2502
|
|
583
|
+
\u2502 (encrypted) \u2502 \u2502 (encrypted) \u2502
|
|
584
|
+
\u2502 \u2502 \u2502 \u2502
|
|
585
|
+
\u2502 \u2601\uFE0F iCloud \u2601\uFE0F \u2502
|
|
586
|
+
\u2502 (passkey syncs) \u2502
|
|
587
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
588
|
+
`},whyBackupMatters:{title:"Why Backup Matters",content:`
|
|
589
|
+
Cryptocurrency is PERMANENT. There's no "reset password" button.
|
|
590
|
+
|
|
591
|
+
REAL SCENARIOS:
|
|
592
|
+
---------------
|
|
593
|
+
|
|
594
|
+
\u{1F4B8} James lost $7.4M in Bitcoin
|
|
595
|
+
- Hard drive thrown away
|
|
596
|
+
- No backup
|
|
597
|
+
- Bitcoin lost forever
|
|
598
|
+
|
|
599
|
+
\u{1F4B8} Stefan lost $200K in Ethereum
|
|
600
|
+
- Forgot password
|
|
601
|
+
- No recovery phrase saved
|
|
602
|
+
- Wallet locked permanently
|
|
603
|
+
|
|
604
|
+
\u{1F4B8} Maria recovered $500K
|
|
605
|
+
- Phone stolen
|
|
606
|
+
- Had encrypted backup
|
|
607
|
+
- Recovered in 5 minutes \u2713
|
|
608
|
+
|
|
609
|
+
THE MATH:
|
|
610
|
+
---------
|
|
611
|
+
|
|
612
|
+
No backup:
|
|
613
|
+
- Lose device = Lose wallet (100% loss)
|
|
614
|
+
- Forget password = Lose wallet (100% loss)
|
|
615
|
+
- Device breaks = Lose wallet (100% loss)
|
|
616
|
+
|
|
617
|
+
With backup:
|
|
618
|
+
- Lose device = Recover from backup \u2713
|
|
619
|
+
- Forget password = Use recovery phrase \u2713
|
|
620
|
+
- Device breaks = Use social recovery \u2713
|
|
621
|
+
|
|
622
|
+
BACKUP LAYERS:
|
|
623
|
+
--------------
|
|
624
|
+
|
|
625
|
+
Layer 1: Passkey (convenience)
|
|
626
|
+
- Auto-syncs
|
|
627
|
+
- Easy to use
|
|
628
|
+
- Platform-dependent
|
|
629
|
+
|
|
630
|
+
Layer 2: Encrypted Backup (universal)
|
|
631
|
+
- Works anywhere
|
|
632
|
+
- Password-protected
|
|
633
|
+
- Portable
|
|
634
|
+
|
|
635
|
+
Layer 3: Social Recovery (ultimate)
|
|
636
|
+
- Friends help you
|
|
637
|
+
- No single point of failure
|
|
638
|
+
- Most secure
|
|
639
|
+
|
|
640
|
+
RECOMMENDATION:
|
|
641
|
+
Use ALL THREE for maximum security!
|
|
642
|
+
`},socialRecoveryExplained:{title:"Social Recovery Explained",content:`
|
|
643
|
+
Social recovery lets trusted friends/family help you recover your wallet.
|
|
644
|
+
|
|
645
|
+
HOW IT WORKS (3-of-5 Example):
|
|
646
|
+
------------------------------
|
|
647
|
+
|
|
648
|
+
1. Your 12-word phrase is split into 5 pieces
|
|
649
|
+
2. Each piece goes to a trusted guardian
|
|
650
|
+
3. Any 3 pieces can reconstruct your phrase
|
|
651
|
+
4. Any 2 pieces reveal NOTHING
|
|
652
|
+
|
|
653
|
+
THE MATH (Shamir Secret Sharing):
|
|
654
|
+
---------------------------------
|
|
655
|
+
|
|
656
|
+
Example: Secret = "apple"
|
|
657
|
+
|
|
658
|
+
Split into 5 shares:
|
|
659
|
+
- Share 1: "x7k9m"
|
|
660
|
+
- Share 2: "p2n4q"
|
|
661
|
+
- Share 3: "w8j3z"
|
|
662
|
+
- Share 4: "r5h1v"
|
|
663
|
+
- Share 5: "c6y2b"
|
|
664
|
+
|
|
665
|
+
Combine any 3 \u2192 Get "apple"
|
|
666
|
+
Have only 2 \u2192 Impossible to recover
|
|
667
|
+
|
|
668
|
+
CHOOSING GUARDIANS:
|
|
669
|
+
------------------
|
|
670
|
+
|
|
671
|
+
\u2713 GOOD Guardians:
|
|
672
|
+
- Family members
|
|
673
|
+
- Close friends
|
|
674
|
+
- Geographically distributed
|
|
675
|
+
- Tech-savvy
|
|
676
|
+
- Long-term relationships
|
|
677
|
+
|
|
678
|
+
\u2717 BAD Guardians:
|
|
679
|
+
- Strangers
|
|
680
|
+
- Same location (house fire risk)
|
|
681
|
+
- Not reliable
|
|
682
|
+
- Can't handle QR codes
|
|
683
|
+
|
|
684
|
+
SECURITY:
|
|
685
|
+
---------
|
|
686
|
+
|
|
687
|
+
Math proves:
|
|
688
|
+
- Need EXACTLY 3 shares
|
|
689
|
+
- 2 shares = 0% information
|
|
690
|
+
- 4 shares = 100% redundancy
|
|
691
|
+
- Guardians can't collude unless threshold met
|
|
692
|
+
|
|
693
|
+
RECOVERY FLOW:
|
|
694
|
+
-------------
|
|
695
|
+
|
|
696
|
+
1. You lose wallet access
|
|
697
|
+
2. Contact 3 guardians
|
|
698
|
+
3. Each provides their share
|
|
699
|
+
4. System combines shares
|
|
700
|
+
5. Wallet reconstructed \u2713
|
|
701
|
+
|
|
702
|
+
Timeline: ~24 hours
|
|
703
|
+
(depends on guardian availability)
|
|
704
|
+
`},encryptedBackupSecurity:{title:"Encrypted Backup Security",content:`
|
|
705
|
+
Your encrypted backup uses military-grade encryption.
|
|
706
|
+
|
|
707
|
+
ENCRYPTION DETAILS:
|
|
708
|
+
------------------
|
|
709
|
+
|
|
710
|
+
Password \u2192 PBKDF2 (310,000 iterations)
|
|
711
|
+
\u2193
|
|
712
|
+
256-bit AES Key
|
|
713
|
+
\u2193
|
|
714
|
+
AES-256-GCM Encryption
|
|
715
|
+
\u2193
|
|
716
|
+
Encrypted Backup
|
|
717
|
+
|
|
718
|
+
WHAT THIS MEANS:
|
|
719
|
+
---------------
|
|
720
|
+
|
|
721
|
+
310,000 iterations:
|
|
722
|
+
- Makes brute-force extremely slow
|
|
723
|
+
- Would take centuries to crack
|
|
724
|
+
- OWASP 2025 standard
|
|
725
|
+
|
|
726
|
+
AES-256-GCM:
|
|
727
|
+
- Used by governments/military
|
|
728
|
+
- Authenticated encryption
|
|
729
|
+
- Cannot be tampered with
|
|
730
|
+
|
|
731
|
+
ATTACK SCENARIOS:
|
|
732
|
+
----------------
|
|
733
|
+
|
|
734
|
+
Q: What if Google Drive is hacked?
|
|
735
|
+
A: Attacker gets encrypted file (useless without password)
|
|
736
|
+
|
|
737
|
+
Q: What if someone gets your password?
|
|
738
|
+
A: Need BOTH password AND backup file
|
|
739
|
+
|
|
740
|
+
Q: What if someone tries to brute-force?
|
|
741
|
+
A: 310,000 iterations makes this impractical
|
|
742
|
+
|
|
743
|
+
Q: Can the backup be tampered with?
|
|
744
|
+
A: No - GCM mode detects any changes
|
|
745
|
+
|
|
746
|
+
PASSWORD STRENGTH:
|
|
747
|
+
-----------------
|
|
748
|
+
|
|
749
|
+
WEAK (\u274C Rejected):
|
|
750
|
+
- "password123"
|
|
751
|
+
- "qwerty"
|
|
752
|
+
- "12345678"
|
|
753
|
+
|
|
754
|
+
GOOD (\u2713 Accepted):
|
|
755
|
+
- "MyDog-Loves-Pizza-2025!"
|
|
756
|
+
- "Tr0ub4dor&3-Extra-Secure"
|
|
757
|
+
- "correct-horse-battery-staple-99"
|
|
758
|
+
|
|
759
|
+
STRONG (\u2713\u2713 Recommended):
|
|
760
|
+
- 16+ characters
|
|
761
|
+
- Mix of upper/lower/numbers/symbols
|
|
762
|
+
- Not in dictionary
|
|
763
|
+
- Unique to this backup
|
|
764
|
+
|
|
765
|
+
STORAGE OPTIONS:
|
|
766
|
+
---------------
|
|
767
|
+
|
|
768
|
+
\u2713 Safe to store encrypted backup in:
|
|
769
|
+
- Google Drive
|
|
770
|
+
- Dropbox
|
|
771
|
+
- iCloud
|
|
772
|
+
- USB drive
|
|
773
|
+
- Email to yourself
|
|
774
|
+
|
|
775
|
+
The encryption makes it safe even in cloud!
|
|
776
|
+
`}}});var he={};A(he,{RecoverySimulator:()=>_,educationalModules:()=>pe,getAllTopics:()=>lt,getExplainer:()=>dt,searchExplainers:()=>ut});var V=u(()=>{"use strict";ct();pt()});w();import{startRegistration as bt}from"@simplewebauthn/browser";function Le(s){return/^0x[a-fA-F0-9]{40}$/.test(s)}function qe(s){return s.length>=3&&s.length<=50}function Ge(s){let e=s.trim().split(/\s+/);return e.length===12||e.length===24}function fe(s){if(!Le(s))throw new Error("Invalid Ethereum address format")}function we(s){if(!qe(s))throw new Error("Username must be between 3 and 50 characters")}function wt(s){if(!Ge(s))throw new Error("Invalid mnemonic: must be 12 or 24 words")}function vt(s){if(s.length<12)return!1;let e=/[A-Z]/.test(s),t=/[a-z]/.test(s),r=/[0-9]/.test(s),n=/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(s);return!(!e||!t||!r||!n||["password","12345678","qwerty","abc123","password123","admin","letmein"].some(i=>s.toLowerCase().includes(i)))}C();function St(){let s=new Uint8Array(32);return crypto.getRandomValues(s),btoa(String.fromCharCode(...s)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function $e(s){try{let{username:e,ethereumAddress:t}=s;we(e),fe(t);let r=new P;if(r.userExists(e))throw new Error("Username already registered");let o={challenge:St(),rp:{name:"w3pk",id:window.location.hostname},user:{id:e,name:e,displayName:e},pubKeyCredParams:[{type:"public-key",alg:-7},{type:"public-key",alg:-257}],authenticatorSelection:{authenticatorAttachment:"platform",userVerification:"required",residentKey:"required",requireResidentKey:!0},timeout:6e4,attestation:"none"},i=await bt({optionsJSON:o}),c=i.response.publicKey;if(!c)throw new Error("Public key not returned from authenticator");r.saveCredential({id:i.id,publicKey:c,username:e,ethereumAddress:t,createdAt:Date.now(),lastUsed:Date.now()}),console.log("[register] Credential response:",i.response);let a=i.response.attestationObject;if(console.log("[register] Attestation object:",a),!a)throw new Error("Attestation object not returned from authenticator");let l=kt(a);return console.log("[register] Attestation buffer length:",l.byteLength),{signature:l}}catch(e){throw new K(e instanceof Error?e.message:"Registration failed",e)}}function kt(s){let e=s.replace(/-/g,"+").replace(/_/g,"/"),t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r.buffer}w();C();import{startAuthentication as At}from"@simplewebauthn/browser";function Et(){let s=new Uint8Array(32);return crypto.getRandomValues(s),btoa(String.fromCharCode(...s)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function Z(){try{let s=new P,t={challenge:Et(),rpId:window.location.hostname,userVerification:"required",timeout:6e4},r=await At({optionsJSON:t}),n=s.getCredentialById(r.id);if(!n)throw new Error("Credential not found");if(!await Pt(r,n))throw new Error("Signature verification failed");s.updateLastUsed(n.id);let i=z(r.response.signature);return{verified:!0,user:{username:n.username,ethereumAddress:n.ethereumAddress,credentialId:n.id},signature:i}}catch(s){throw new E(s instanceof Error?s.message:"Authentication failed",s)}}async function Pt(s,e){try{let t=z(e.publicKey),r=await crypto.subtle.importKey("spki",t,{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]),n=z(s.response.authenticatorData),o=s.response.clientDataJSON,i;o.startsWith("eyJ")?i=atob(o.replace(/-/g,"+").replace(/_/g,"/")):i=o;let c=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(i)),a=new Uint8Array(n.byteLength+c.byteLength);a.set(new Uint8Array(n),0),a.set(new Uint8Array(c),n.byteLength);let l=z(s.response.signature),p=Ct(new Uint8Array(l));return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},r,p,a)}catch(t){return console.error("Signature verification error:",t),!1}}function z(s){let e=s.replace(/-/g,"+").replace(/_/g,"/"),t=atob(e),r=new Uint8Array(t.length);for(let n=0;n<t.length;n++)r[n]=t.charCodeAt(n);return r.buffer}function Ct(s){let e=2;e++;let t=s[e++];t>32&&(e++,t--);let r=s.slice(e,e+t);e+=t,e++;let n=s[e++];n>32&&(e++,n--);let o=s.slice(e,e+n),i=new Uint8Array(64);return i.set(r,32-r.length),i.set(o,64-o.length),i.buffer}w();var Rt="Web3PasskeyWallet",xt=1,v="wallets",X=class{constructor(){this.db=null}async init(){return new Promise((e,t)=>{let r=indexedDB.open(Rt,xt);r.onerror=()=>t(new h("Failed to open database",r.error)),r.onsuccess=()=>{this.db=r.result,e()},r.onupgradeneeded=()=>{let n=r.result;n.objectStoreNames.contains(v)||n.createObjectStore(v,{keyPath:"ethereumAddress"})}})}async store(e){return this.db||await this.init(),new Promise((t,r)=>{let i=this.db.transaction([v],"readwrite").objectStore(v).put(e);i.onerror=()=>r(new h("Failed to store wallet data",i.error)),i.onsuccess=()=>t()})}async retrieve(e){return this.db||await this.init(),new Promise((t,r)=>{let i=this.db.transaction([v],"readonly").objectStore(v).get(e);i.onerror=()=>r(new h("Failed to retrieve wallet data",i.error)),i.onsuccess=()=>t(i.result||null)})}async delete(e){return this.db||await this.init(),new Promise((t,r)=>{let i=this.db.transaction([v],"readwrite").objectStore(v).delete(e);i.onerror=()=>r(new h("Failed to delete wallet data",i.error)),i.onsuccess=()=>t()})}async clear(){return this.db||await this.init(),new Promise((e,t)=>{let o=this.db.transaction([v],"readwrite").objectStore(v).clear();o.onerror=()=>t(new h("Failed to clear wallet data",o.error)),o.onsuccess=()=>e()})}};w();import{ethers as ee}from"ethers";function ve(){try{let s=ee.Wallet.createRandom().mnemonic;if(!s)throw new Error("Failed to generate mnemonic");let e=s.phrase;return{address:ee.HDNodeWallet.fromPhrase(e,void 0,"m/44'/60'/0'/0/0").address,mnemonic:e}}catch(s){throw new d("Wallet generation failed",s)}}function It(s){try{if(!s||s.trim().split(/\s+/).length<12)throw new Error("Invalid mnemonic: must be at least 12 words");return ee.HDNodeWallet.fromPhrase(s.trim(),void 0,"m/44'/60'/0'/0/0")}catch(e){throw new d(`Wallet creation failed: ${e instanceof Error?e.message:"Invalid mnemonic"}`,e)}}function be(s,e=0){try{if(!s||s.trim().split(/\s+/).length<12)throw new Error("Invalid mnemonic: must be at least 12 words");if(e<0||!Number.isInteger(e))throw new Error("Index must be a non-negative integer");let t=`m/44'/60'/0'/0/${e}`,r=ee.HDNodeWallet.fromPhrase(s.trim(),void 0,t);return{address:r.address,privateKey:r.privateKey}}catch(t){throw new d(`HD wallet derivation failed: ${t instanceof Error?t.message:"Unknown error"}`,t)}}ne();w();w();import{ethers as m}from"ethers";function U(s){try{let e=m.HDNodeWallet.fromPhrase(s,void 0,"m/44'/60'/1'/0/0"),t=m.HDNodeWallet.fromPhrase(s,void 0,"m/44'/60'/1'/0/1"),r=t.signingKey.compressedPublicKey,n=e.signingKey.compressedPublicKey,o=r+n.slice(2),i=Mt(e.signingKey.publicKey,t.signingKey.publicKey);return{stealthMetaAddress:o,spendingPubKey:r,viewingPubKey:n,viewingKey:e.privateKey,spendingKey:t.privateKey,metaAddress:i}}catch(e){throw new y("Failed to derive stealth keys",e)}}function ke(s){try{let e="0x"+s.slice(2,68),t="0x"+s.slice(68),r=m.Wallet.createRandom(),n=r.signingKey.compressedPublicKey,o=Ee(r.privateKey,t),i=m.keccak256(o),c="0x"+i.slice(2,4),a=Ve(e,_e(i));return{stealthAddress:Qe(a),ephemeralPubKey:n,viewTag:c,ephemeralPubkey:n}}catch(e){throw new y("Failed to generate stealth address",e)}}function se(s,e,t,r,n){try{let o=Ee(s,t),i=m.keccak256(o);if(n&&("0x"+i.slice(2,4)).toLowerCase()!==n.toLowerCase())return{isForUser:!1};let c=Ve(e,_e(i)),a=Qe(c);return a.toLowerCase()!==r.toLowerCase()?{isForUser:!1}:{isForUser:!0,stealthAddress:a}}catch{return{isForUser:!1}}}function Ae(s,e,t){try{let r=Ee(s,t),n=m.keccak256(r);return Ft(e,n)}catch(r){throw new y("Failed to compute stealth private key",r)}}function Ee(s,e){try{return new m.Wallet(s).signingKey.computeSharedSecret(e)}catch(t){throw new y("Failed to compute shared secret",t)}}function _e(s){try{return new m.Wallet(s).signingKey.compressedPublicKey}catch(e){throw new y("Failed to multiply generator by scalar",e)}}function Ve(s,e){try{let t=m.SigningKey.computePublicKey(s,!1),r=m.SigningKey.computePublicKey(e,!1),n=BigInt("0x"+t.slice(4,68)),o=BigInt("0x"+t.slice(68)),i=BigInt("0x"+r.slice(4,68)),c=BigInt("0x"+r.slice(68)),a=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F");if(n===i&&o===c){let yt=3n*n*n%a,mt=2n*o%a,me=yt*He(mt,a)%a,Ne=(me*me-2n*n)%a,gt=(me*(n-Ne)-o)%a;return Ye((Ne+a)%a,(gt+a)%a)}let l=((c-o)%a+a)%a,p=((i-n)%a+a)%a,M=l*He(p,a)%a,ye=(M*M-n-i)%a,ht=(M*(n-ye)-o)%a;return Ye((ye+a)%a,(ht+a)%a)}catch(t){throw new y("Failed to add public keys",t)}}function Ft(s,e){try{let t=BigInt("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"),r=BigInt(s),n=BigInt(e);return"0x"+((r+n)%t).toString(16).padStart(64,"0")}catch(t){throw new y("Failed to add private keys",t)}}function Ye(s,e){return"0x"+(e%2n===0n?"02":"03")+s.toString(16).padStart(64,"0")}function He(s,e){s=(s%e+e)%e;let[t,r]=[s,e],[n,o]=[1n,0n];for(;r!==0n;){let i=t/r;[t,r]=[r,t-i*r],[n,o]=[o,n-i*o]}return(n%e+e)%e}function Qe(s){try{let e=m.SigningKey.computePublicKey(s,!1),t=m.keccak256("0x"+e.slice(4));return m.getAddress("0x"+t.slice(-40))}catch(e){throw new y("Failed to derive address from public key",e)}}function Mt(s,e){let t=m.solidityPackedKeccak256(["bytes","bytes"],[s,e]);return m.getAddress("0x"+t.slice(26))}function Kt(s,e,t,r){let n=new m.Wallet(e);return se(s,n.signingKey.compressedPublicKey,t,r).isForUser}var W=class{constructor(e,t){this.getMnemonic=t}async generateStealthAddress(e){try{let t=await this.getMnemonic(e?.requireAuth),r=U(t),n=ke(r.stealthMetaAddress);return{stealthAddress:n.stealthAddress,ephemeralPublicKey:n.ephemeralPubKey,viewTag:n.viewTag}}catch(t){throw new g("Failed to generate stealth address","STEALTH_GENERATION_ERROR",t)}}async parseAnnouncement(e,t){try{let r=await this.getMnemonic(t?.requireAuth),n=U(r),o=se(n.viewingKey,n.spendingPubKey,e.ephemeralPublicKey,e.stealthAddress,e.viewTag);if(!o.isForUser)return{isForUser:!1};let i=Ae(n.viewingKey,n.spendingKey,e.ephemeralPublicKey);return{isForUser:!0,stealthAddress:o.stealthAddress,stealthPrivateKey:i}}catch(r){throw new g("Failed to parse announcement","ANNOUNCEMENT_PARSE_ERROR",r)}}async scanAnnouncements(e,t){let r=[];for(let n of e){let o=await this.parseAnnouncement(n,t);o.isForUser&&r.push(o)}return r}async getKeys(e){try{let t=await this.getMnemonic(e?.requireAuth);return U(t)}catch(t){throw new g("Failed to get stealth keys","STEALTH_KEYS_ERROR",t)}}async getStealthMetaAddress(e){try{return(await this.getKeys(e)).stealthMetaAddress}catch(t){throw new g("Failed to get stealth meta-address","STEALTH_META_ADDRESS_ERROR",t)}}get isAvailable(){return!0}};var oe=class{constructor(e=1){this.session=null;this.sessionDuration=e*60*60*1e3}startSession(e,t){let r=Date.now()+this.sessionDuration;this.session={mnemonic:e,expiresAt:r,credentialId:t}}getMnemonic(){return this.session?Date.now()>this.session.expiresAt?(this.clearSession(),null):this.session.mnemonic:null}getCredentialId(){return this.session?Date.now()>this.session.expiresAt?(this.clearSession(),null):this.session.credentialId:null}isActive(){return this.getMnemonic()!==null}getRemainingTime(){return this.session?Date.now()>this.session.expiresAt?(this.clearSession(),0):Math.floor((this.session.expiresAt-Date.now())/1e3):0}extendSession(){if(!this.session)throw new Error("No active session to extend");if(Date.now()>this.session.expiresAt)throw this.clearSession(),new Error("Session expired, cannot extend");this.session.expiresAt=Date.now()+this.sessionDuration}clearSession(){this.session&&(this.session.mnemonic="0".repeat(this.session.mnemonic.length)),this.session=null}setSessionDuration(e){this.sessionDuration=e*60*60*1e3}};var Pe={debug:!1,sessionDuration:1,onError:s=>{Pe.debug&&console.error("[w3pk]",s)}};w();var je="https://chainid.network/chains.json";var ie=null,Ot=[/\$\{[\w_]+\}/i,/\{[\w_]+\}/i,/<[\w_]+>/i,/YOUR[-_]?API[-_]?KEY/i,/INSERT[-_]?API[-_]?KEY/i,/API[-_]?KEY[-_]?HERE/i];function Ut(s){return Ot.some(e=>e.test(s))}async function Wt(s=je){let e=await fetch(s);if(!e.ok)throw new Error(`Failed to fetch chains data: ${e.status} ${e.statusText}`);return await e.json()}async function Nt(s){let e=s?.chainsJsonUrl??je,t=s?.cacheDuration??36e5,r=Date.now();if(ie&&r-ie.timestamp<t)return ie.data;let n=await Wt(e);return ie={data:n,timestamp:r},n}async function Je(s,e){let r=(await Nt(e)).find(n=>n.chainId===s);return r?r.rpc.filter(n=>!Ut(n)&&!n.startsWith("wss://")&&!n.startsWith("ws://")):[]}var Lt=new Set([1,10,8453,42161,57073,100,42220,137,42,15,40,41,44,46,47,50,51,56,61,71,82,83,95,97,112,123,130,146,151,153,171,180,183,185,195,215,228,247,248,252,261,267,291,293,311,332,336,395,401,416,466,480,488,510,545,634,647,648,747,831,919,938,945,957,964,970,980,995,997,1001,1003,1024,1030,1114,1125,1135,1149,1188,1284,1285,1287,1300,1301,1315,1337,1338,1339,1424,1514,1687,1727,1729,1740,1750,1829,1868,1946,1961,1962,1969,1989,1995,2017,2020,2031,2043,2109,2241,2340,2345,2440,2522,2559,2649,3068,3109,3338,3502,3799,3888,3889,4e3,4048,4078,4162,4201,4202,4460,4488,4661,4689,4690,4888,5e3,5003,5124,5234,5330,5424,5522,6283,6342,6398,6678,6806,6934,6942,6969,7117,7171,7200,7208,7368,7518,7668,7672,7744,7771,7869,7897,8008,8118,8217,8408,8700,8726,8727,8844,8880,8881,8882,8889,9372,9496,9700,9745,9746,9899,9990,9996,10011,10085,10143,10200,11221,11501,11504,11891,13370,14853,16602,16661,17e3,18880,18881,19991,21e3,21816,21912,25327,32323,33401,34443,41923,42170,43111,44787,47805,48898,48900,49049,49088,5e4,50312,53302,53456,53457,55244,56288,59141,60808,60850,62320,62850,64002,71402,72080,73114,73115,75338,78281,80002,80008,80069,80094,80451,80931,84532,88899,91342,92278,94524,96970,97476,97477,98985,100021,100501,101010,102030,102031,102032,112358,120893,121212,121213,121214,121215,129399,161803,175188,192940,193939,198989,212013,222222,240241,325e3,355110,355113,421614,555777,560048,656476,713715,743111,747474,763373,763375,777777,806582,808813,810180,839999,888991,2019775,2222222,4278608,5734951,6666689,6985385,7080969,7777777,9999999,11142220,11155111,11155420,11155931,16969696,19850818,20180427,20250825,28122024,34949059,37084624,52164803,61022448,79479957,96969696,420420421,420420422,888888888,974399131,999999999,1020352220,1273227453,1313161560,1350216234,1380996178,1417429182,1444673419,1482601649,1564830818,2046399126,11297108099,11297108109,88153591557,123420000220,123420001114]);async function qt(s,e=1e4){try{let t=new AbortController,r=setTimeout(()=>t.abort(),e),n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},signal:t.signal,body:JSON.stringify({jsonrpc:"2.0",method:"eth_estimateGas",params:[{from:"0xdeadbeef00000000000000000000000000000000",to:"0xdeadbeef00000000000000000000000000000000",data:"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",value:"0x0"},"latest",{"0xdeadbeef00000000000000000000000000000000":{code:"0xef01000000000000000000000000000000000000000001"}}],id:1})});if(clearTimeout(r),!n.ok)return!1;let o=await n.json();if(o.error){let i=(o.error.message||"").toLowerCase();return!["unsupported","not supported","unknown","invalid","unrecognized","does not support","not implemented"].some(a=>i.includes(a))}return o.result!==void 0}catch{return!1}}async function ze(s,e,t){if(Lt.has(s))return!0;let r=t?.maxEndpoints||3,n=t?.timeout||1e4;try{let o=await e(s);if(o.length===0)return!1;let i=o.slice(0,r);for(let c of i)if(await qt(c,n))return!0;return!1}catch{return!1}}var Q=class{constructor(e={}){this.currentUser=null;this.currentWallet=null;this.config={...Pe,...e},this.walletStorage=new X,this.sessionManager=new oe(e.sessionDuration||1),e.stealthAddresses!==void 0&&(this.stealth=new W(e.stealthAddresses,t=>this.getMnemonicFromSession(t)))}async loadZKModule(){if(this.zkModule)return this.zkModule;try{let e=new Function("path","return import(path)"),{ZKProofModule:t}=await e("w3pk/zk"),r=this.config.zkProofs||{};return this.zkModule=new t(r),this.zkModule}catch{throw new Error("ZK module not available. Install dependencies: npm install snarkjs circomlibjs")}}async getMnemonicFromSession(e=!1){if(!e){let l=this.sessionManager.getMnemonic();if(l)return l}if(!this.currentUser)throw new d("Must be authenticated. Call login() first.");let t=await this.walletStorage.retrieve(this.currentUser.ethereumAddress);if(!t)throw new d("No wallet found. Generate a wallet first.");if(!(await Z()).user)throw new d("Authentication failed");let i=new(await Promise.resolve().then(()=>(C(),O))).CredentialStorage().getCredentialById(t.credentialId)?.publicKey,c=await R(t.credentialId,i),a=await re(t.encryptedMnemonic,c);return this.sessionManager.startSession(a,t.credentialId),a}async register(e){try{this.currentWallet?.address||await this.generateWallet();let t=this.currentWallet.address,r=this.currentWallet.mnemonic;await $e({username:e.username,ethereumAddress:t}),this.currentUser={id:t,username:e.username,displayName:e.username,ethereumAddress:t};let o=new(await Promise.resolve().then(()=>(C(),O))).CredentialStorage().getCredentialByAddress(t);if(!o)throw new d("Credential not found after registration");let i=o.id,c=o.publicKey,a=await R(i,c),l=await te(r,a);return await this.walletStorage.store({ethereumAddress:this.currentUser.ethereumAddress,encryptedMnemonic:l,credentialId:i,createdAt:Date.now()}),this.sessionManager.startSession(r,i),this.currentWallet={address:t,mnemonic:r},this.config.onAuthStateChanged?.(!0,this.currentUser),{address:t,username:e.username}}catch(t){throw this.config.onError?.(t),t}}async login(){try{let e=await Z();if(!e.verified||!e.user)throw new E("Login failed");this.currentUser={id:e.user.ethereumAddress,username:e.user.username,displayName:e.user.username,ethereumAddress:e.user.ethereumAddress};let t=await this.walletStorage.retrieve(this.currentUser.ethereumAddress);if(!t)throw new d("No wallet found for this user. You may need to register first.");let o=new(await Promise.resolve().then(()=>(C(),O))).CredentialStorage().getCredentialById(t.credentialId)?.publicKey,i=await R(t.credentialId,o),c=await re(t.encryptedMnemonic,i);return this.sessionManager.startSession(c,t.credentialId),this.config.onAuthStateChanged?.(!0,this.currentUser),this.currentUser}catch(e){throw this.config.onError?.(e),e}}async logout(){this.currentUser=null,this.currentWallet=null,this.sessionManager.clearSession(),this.config.onAuthStateChanged?.(!1,void 0)}get isAuthenticated(){return this.currentUser!==null}get user(){return this.currentUser}async generateWallet(){try{let e=ve();return this.currentWallet={address:e.address,mnemonic:e.mnemonic},{mnemonic:e.mnemonic}}catch(e){throw this.config.onError?.(e),new d("Failed to generate wallet",e)}}async deriveWallet(e,t){try{if(!this.currentUser)throw new d("Must be authenticated to derive wallet");let r=await this.getMnemonicFromSession(t?.requireAuth),n=be(r,e);return{address:n.address,privateKey:n.privateKey}}catch(r){throw this.config.onError?.(r),new d("Failed to derive wallet",r)}}async exportMnemonic(e){try{if(!this.currentUser)throw new d("Must be authenticated to export mnemonic");return await this.getMnemonicFromSession(e?.requireAuth)}catch(t){throw this.config.onError?.(t),new d("Failed to export mnemonic",t)}}async importMnemonic(e){try{if(!this.currentUser)throw new d("Must be authenticated to import mnemonic");if(!e||e.trim().split(/\s+/).length<12)throw new d("Invalid mnemonic: must be at least 12 words");let t=await Z();if(!t.user)throw new d("Authentication failed");let r=t.user.credentialId,i=new(await Promise.resolve().then(()=>(C(),O))).CredentialStorage().getCredentialById(r)?.publicKey,c=await R(r,i),a=await te(e.trim(),c);await this.walletStorage.store({ethereumAddress:this.currentUser.ethereumAddress,encryptedMnemonic:a,credentialId:r,createdAt:Date.now()}),this.currentWallet={address:this.currentUser.ethereumAddress,mnemonic:e.trim()},this.sessionManager.startSession(e.trim(),r)}catch(t){throw this.config.onError?.(t),new d("Failed to import mnemonic",t)}}async signMessage(e,t){try{if(!this.currentUser)throw new d("Must be authenticated to sign message");let r=await this.getMnemonicFromSession(t?.requireAuth),{Wallet:n}=await import("ethers");return await n.fromPhrase(r).signMessage(e)}catch(r){throw this.config.onError?.(r),new d("Failed to sign message",r)}}async getEndpoints(e){return Je(e)}async supportsEIP7702(e,t){return ze(e,this.getEndpoints.bind(this),t)}get zk(){return new Proxy({},{get:(e,t)=>async(...r)=>(await this.loadZKModule())[t](...r)})}async getBackupStatus(){if(!this.currentUser)throw new d("Must be authenticated to check backup status");let{BackupManager:e}=await Promise.resolve().then(()=>(I(),F));return new e().getBackupStatus(this.currentUser.ethereumAddress)}async createZipBackup(e,t){if(!this.currentUser)throw new d("Must be authenticated to create backup");let r=await this.getMnemonicFromSession(!0),{BackupManager:n}=await Promise.resolve().then(()=>(I(),F));return new n().createZipBackup(r,this.currentUser.ethereumAddress,{password:e,...t})}async createQRBackup(e,t){if(!this.currentUser)throw new d("Must be authenticated to create backup");let r=await this.getMnemonicFromSession(!0),{BackupManager:n}=await Promise.resolve().then(()=>(I(),F));return new n().createQRBackup(r,this.currentUser.ethereumAddress,{password:e,...t})}async setupSocialRecovery(e,t){if(!this.currentUser)throw new d("Must be authenticated to set up social recovery");let r=await this.getMnemonicFromSession(!0),{SocialRecoveryManager:n}=await Promise.resolve().then(()=>(G(),le));return new n().setupSocialRecovery(r,this.currentUser.ethereumAddress,e,t)}async generateGuardianInvite(e){let{SocialRecoveryManager:t}=await Promise.resolve().then(()=>(G(),le)),r=new t,n=r.getSocialRecoveryConfig();if(!n)throw new d("Social recovery not configured");let o=n.guardians.find(i=>i.id===e);if(!o)throw new d("Guardian not found");return r.generateGuardianInvite(o)}async recoverFromGuardians(e){let{SocialRecoveryManager:t}=await Promise.resolve().then(()=>(G(),le));return new t().recoverFromGuardians(e)}async restoreFromBackup(e,t){let{BackupManager:r}=await Promise.resolve().then(()=>(I(),F));return new r().restoreFromZipBackup(e,t)}async restoreFromQR(e,t){let{BackupManager:r}=await Promise.resolve().then(()=>(I(),F));return new r().restoreFromQR(e,t)}async getSyncStatus(){let{DeviceManager:e}=await Promise.resolve().then(()=>(ue(),We));return new e().getSyncStatus()}async detectSyncCapabilities(){let{PlatformDetector:e}=await Promise.resolve().then(()=>(ue(),We));return new e().detectSyncCapabilities()}async simulateRecoveryScenario(e){if(!this.currentUser)throw new d("Must be authenticated to run recovery simulation");let t=await this.getBackupStatus(),{RecoverySimulator:r}=await Promise.resolve().then(()=>(V(),he));return new r().simulateScenario(e,t)}async runRecoveryTest(){if(!this.currentUser)throw new d("Must be authenticated to run recovery test");let e=await this.getBackupStatus(),{RecoverySimulator:t}=await Promise.resolve().then(()=>(V(),he));return new t().runInteractiveTest(e)}async getEducation(e){let{getExplainer:t}=await Promise.resolve().then(()=>(V(),he)),r=t(e);if(!r)throw new d(`Unknown education topic: ${e}`);return r}hasActiveSession(){return this.sessionManager.isActive()}getSessionRemainingTime(){return this.sessionManager.getRemainingTime()}extendSession(){try{this.sessionManager.extendSession()}catch(e){throw new d("Cannot extend session",e)}}clearSession(){this.sessionManager.clearSession()}setSessionDuration(e){this.sessionManager.setSessionDuration(e)}get version(){return"0.7.2"}};w();I();G();ue();V();function $t(s={}){return new Q(s)}var hn=$t;export{ge as ApiError,E as AuthenticationError,L as BackupManager,x as BackupStorage,y as CryptoError,Y as DeviceManager,H as PlatformDetector,_ as RecoverySimulator,K as RegistrationError,q as SocialRecoveryManager,W as StealthAddressModule,h as StorageError,$ as VaultSync,d as WalletError,Q as Web3Passkey,g as Web3PasskeyError,fe as assertEthereumAddress,wt as assertMnemonic,we as assertUsername,Kt as canControlStealthAddress,se as checkStealthAddress,Ae as computeStealthPrivateKey,It as createWalletFromMnemonic,$t as createWeb3Passkey,hn as default,U as deriveStealthKeys,be as deriveWalletFromMnemonic,ve as generateBIP39Wallet,ke as generateStealthAddress,lt as getAllTopics,dt as getExplainer,vt as isStrongPassword,ut as searchExplainers,Le as validateEthereumAddress,Ge as validateMnemonic,qe as validateUsername};
|
|
6
777
|
//# sourceMappingURL=index.mjs.map
|