{"record":{"id":"b28ccb696338561b","repo":"linera-io/linera-protocol","slug":"invalid-owner-address-b28ccb","errorCode":null,"errorMessage":"Invalid owner address","messagePattern":"Invalid owner address","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/@linera/client/src/signer/WebCryptoEd25519.ts","lineNumber":150,"sourceCode":"    const buf = new Uint8Array(value).buffer as ArrayBuffer;\n    const sig = await crypto.subtle.sign(\"Ed25519\", this.record.privateKey, buf);\n    return \"0x\" + bytesToHex(new Uint8Array(sig));\n  }\n\n  async getPublicKey(owner: string): Promise<string> {\n    this.assertOwner(owner);\n    return \"0x\" + bytesToHex(this.record.publicKey);\n  }\n\n  async containsKey(owner: string): Promise<boolean> {\n    // record.owner is canonical lowercase; only normalize the caller side.\n    return owner.toLowerCase() === this.record.owner;\n  }\n\n  private assertOwner(owner: string): void {\n    // record.owner is canonical lowercase; only normalize the caller side.\n    if (owner.toLowerCase() !== this.record.owner) {\n      throw new Error(\"Invalid owner address\");\n    }\n  }\n}\n\nconst DB_NAME = \"linera-signer\";\nconst STORE_NAME = \"keys\";\nconst DB_VERSION = 1;\n\nfunction openDb(): Promise<IDBDatabase> {\n  return new Promise((resolve, reject) => {\n    const req = indexedDB.open(DB_NAME, DB_VERSION);\n    req.onupgradeneeded = () => {\n      const db = req.result;\n      if (!db.objectStoreNames.contains(STORE_NAME)) {\n        db.createObjectStore(STORE_NAME);\n      }\n    };\n    // Fires when another open connection on a lower version blocks this upgrade.","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/web/@linera/client/src/signer/WebCryptoEd25519.ts#L132-L168","documentation":"After confirming the wallet holds a key for the owner, prepare_for_owner validates that the owner may actually propose: ownership.can_propose_in_multi_leader_round(&owner) must be true, i.e. the owner is in the chain's regular/super owners, or the chain explicitly allows any key via open_multi_leader_rounds. Error::NotAnOwner means the owner is a nobody on this chain and public rounds are closed, so proposals from them would be rejected by validators anyway.","triggerScenarios":"Calling prepare_for_owner on a chain where the owner is not listed in ownership and open_multi_leader_rounds is disabled. Producers: assign_new_chain_to_key / test_open_multi_leader_rounds with a non-owner key; a client whose owner was removed from ownership (weight 0) but still tries to propose.","commonSituations":"Multi-leader chains created by a faucet without open rounds enabled; owner rotation where the old owner still runs a client; copy-pasting an owner address instead of registering it on the chain first.","solutions":["Register the owner on the chain first: call share_ownership(new_owner, weight) from an existing owner, or add it via an ownership update operation.","If the chain is meant to be openly proposeable, create/configure it with open_multi_leader_rounds (faucet parameter) so can_propose_in_multi_leader_round passes for any signer.","Switch the client to one of the chain's actual owners (check chain_info().manager.ownership.owners).","If you were removed as owner, ask a current owner to re-add you; do not retry with the stale key."],"exampleFix":"// before: non-owner key attempts to propose\nlet info = client.prepare_for_owner(not_an_owner).await?; // NotAnOwner\n\n// after: an existing owner shares ownership first\nowner_client.share_ownership(not_an_owner, 100).await?;\nlet info = client.prepare_for_owner(not_an_owner).await?;","handlingStrategy":"validation","validationCode":"// Confirm the owner may propose (owner or open multi-leader rounds) beforehand\nlet ownership = client.chain_info().await?.manager.ownership;\nif !ownership.can_propose_in_multi_leader_round(&owner) {\n    return Err(anyhow::anyhow!(\"owner {owner} cannot propose on {}; register it or open multi-leader rounds\", client.chain_id()));\n}","typeGuard":"pub async fn can_owner_propose(client: &ChainClient, owner: AccountOwner) -> Result<bool, Error> {\n    Ok(client.chain_info().await?.manager.ownership.can_propose_in_multi_leader_round(&owner))\n}","tryCatchPattern":"match client.prepare_for_owner(owner).await {\n    Ok(info) => info,\n    Err(Error::NotAnOwner(chain_id)) => {\n        // register the owner via share_ownership from an existing owner client, then retry\n        return Err(anyhow::anyhow!(\"owner not registered on {chain_id}; call share_ownership first\"));\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Register new owners with share_ownership before handing clients their keys.","Create publicly proposeable chains with open_multi_leader_rounds when that is the intent.","After ownership changes, refresh clients' cached ChainInfo.","Log chain ownership alongside proposal failures to catch rotation mistakes early."],"tags":["linera","chain-ownership","authorization","multi-leader"],"backgroundTag":"not-an-owner","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}