( walletAddress: string, passphrase: string, dbPath: string )
| 86 | * Used when setting up a new device. |
| 87 | */ |
| 88 | export async function pullAndReconstruct( |
| 89 | walletAddress: string, |
| 90 | passphrase: string, |
| 91 | dbPath: string |
| 92 | ): Promise<{ factCount: number; version: number }> { |
| 93 | // Step 1: Fetch identity to get salt |
| 94 | const identity = await fetchIdentity(walletAddress, MAX_PULL_IDENTITY_BYTES); |
| 95 | if (!identity) { |
| 96 | throw new Error( |
| 97 | "No identity found on Arweave for this wallet. " + |
| 98 | "Make sure you ran `sharedcontext init` on another device and pushed the identity." |
| 99 | ); |
| 100 | } |
| 101 | |
| 102 | // Step 2: Derive key from passphrase + salt |
| 103 | const key = deriveKey(passphrase, identity.salt); |
| 104 | |
| 105 | // Step 3: Verify we can decrypt the private key (validates passphrase) |
| 106 | try { |
| 107 | decrypt(identity.encryptedPrivateKey, key); |
| 108 | } catch { |
| 109 | throw new Error("Wrong passphrase. Decryption failed."); |
| 110 | } |
| 111 | |
| 112 | // Step 4: Query all shards |
| 113 | const allShards = await queryShards(walletAddress); |
| 114 | const dataShards = allShards.filter( |
| 115 | (s) => s.type === "delta" || s.type === "snapshot" |
| 116 | ); |
| 117 | |
| 118 | if (dataShards.length === 0) { |
| 119 | // No shards yet, just set up empty db |
| 120 | const db = openDatabase(dbPath); |
| 121 | setMeta(db, "current_version", "0"); |
| 122 | setMeta(db, "wallet_address", walletAddress); |
| 123 | db.close(); |
| 124 | return { factCount: 0, version: 0 }; |
| 125 | } |
| 126 | |
| 127 | // Step 5: Find the latest snapshot (if any) and only fetch shards after it |
| 128 | const latestSnapshot = findLatestSnapshot(dataShards); |
| 129 | const shardsToFetch = latestSnapshot |
| 130 | ? dataShards.filter((s) => s.version >= latestSnapshot.version) |
| 131 | : dataShards; |
| 132 | |
| 133 | // Step 6: Download and process shards |
| 134 | const decryptedShards: Shard[] = []; |
| 135 | for (const shardInfo of shardsToFetch) { |
| 136 | try { |
| 137 | const encrypted = await downloadShard( |
| 138 | shardInfo.txId, |
| 139 | MAX_PULL_DATA_SHARD_BYTES |
| 140 | ); |
| 141 | |
| 142 | // Signature is mandatory for all data shards. |
| 143 | if (!shardInfo.signature) { |
| 144 | console.warn( |
| 145 | `Skipping shard v${shardInfo.version}: missing signature.` |
no test coverage detected