fix: compare INDEX ownership by key, not by rendered address - #26
Merged
Conversation
The author stored in a TELA INDEX comes from the DVM's ADDRESS_STRING, which builds it with rpc.NewAddressFromKeys and leaves Mainnet true on every chain -- consensus cannot depend on which network a node thinks it is on. walletapi renders the same key through the wallet's network flag, so on the simulator it says deto1... A DERO address is bech32, prefix and checksum both carry the network, so the two strings never matched and update-index was refused on the simulator, always, including for the wallet that installed the INDEX. Decide ownership on the decoded public key instead. The contract's own gate compares address() to address() and was never affected; mainnet, where both renderings agree, is unchanged. Also applies the same comparison to the isOwner flag in GetINDEXInfo, and logs both values when an update is refused.
DHEBP
approved these changes
Aug 29, 2026
DHEBP
left a comment
Owner
There was a problem hiding this comment.
This is right, merging. Good catch that ADDRESS_STRING is the source, I'd have gone looking at the wallet renderer first.
I reverted sameINDEXAuthor to a plain string compare to check your test, and it fails on both cross-network cases and the integrated one. It's pinning the fix.
Outside this PR: content_filter.go:533 does the same bare compare for author filter rules, so a rule written with a deto1 address never matches an app storing dero1. I'll file that separately.
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this fixes
On the simulator, updating a TELA INDEX is impossible. Every attempt is refused with:
including from the wallet that installed the INDEX a minute earlier.
GetINDEXInforeturnsisOwner: falsefor the same reason, so the interface also shows an owner their own INDEX as not theirs.How I ran into it
Developing a TELA app against HOLOGRAM's simulator — a set of DOCs under one INDEX. One of those DOCs has to carry the INDEX's own SCID as a constant, so that the DOC can point back at the application it belongs to. That SCID does not exist until the DOCs are installed, so the constant can only be filled in afterwards. The loop is: install the DOCs, install the INDEX, note its SCID, write it into that one DOC, redeploy that DOC, update the INDEX.
The last step never worked — not once, with any wallet. Since being repointable is the entire purpose of an INDEX, the only workaround was to leave that constant empty on the simulator and fill it in at mainnet deployment. That means shipping to mainnet the one step that had never been exercised, which is exactly backwards.
Root cause
The two strings being compared are produced by two different renderers, and they only agree on mainnet.
What the INDEX stores.
TELA-INDEX-1.basdoesSTORE("owner", address()), whereaddress()isADDRESS_STRING(SIGNER()). In derohe,dvm_address_string(dvm/dvm_functions.go) builds that string as:rpc.NewAddressFromKeyssetsMainnet: true, and nothing resets it. That is deliberate and correct — consensus cannot depend on which network a node believes it is running on — so a contract recordsdero1…on every chain, the simulator included.What the wallet renders.
wallet.GetAddress().String()goes through walletapi using the wallet's own network flag, which HOLOGRAM sets fromIsInSimulatorMode(). On the simulator that isdeto1….A DERO address is bech32: the human-readable prefix names the network, and the six-character checksum is computed over that prefix. So one key renders as two strings that differ at both ends:
(Vector taken from this repo's own
network_mismatch_guard_test.go; the second line is the first re-rendered withMainnet = false.) Everything between those two marks is identical, and both strings decode to the same compressed point.existingIndex.Author != walletAddris therefore true forever on the simulator.The contract's own gate was never affected.
UpdateCodechecksIF LOAD("owner") == address()— one DVM rendering against another, always consistent. The chain would have accepted every one of these updates. Only the client-side pre-check refused them.This is also why
tela-clirefuses the same operation: it carries the identical comparison (see Not addressed here).The change
tela_service.goonly, no new imports (rpcwas already imported):sameINDEXAuthor(stored, walletAddr string) bool— decodes both strings withrpc.NewAddressand comparesCompressed(), the 33-byte public key.UpdateINDEXuses it instead of!=, and logs both values when it refuses, so the next refusal is diagnosable instead of opaque.GetINDEXInfouses it forisOwner.New file
tela_index_owner_test.gocovering the helper.Why compare keys rather than re-render both on one network
Normalising both to one network works too (
BaseAddress(), setMainnet, compare strings). I chose the key because re-rendering makes the check depend on getting a network flag right at the moment of comparison — which is the class of bug being fixed here.Compressed()returns the identity the contract is actually gating on, and it is incidentally indifferent to an integrated address, since a payment ID says nothing about ownership.Scope and risk
stored == walletAddrfast path returns before any decoding happens.address(). Nothing that was refused for a real reason is now accepted.false. The"anon"(immutable, ring 16+) case is caught by the existing check above this one and would returnfalsehere regardless.Not addressed here
tela-clihas the identical defect, in four places —index.Author != app.wallet.disk.GetAddress().String()incmd/tela-cli/main.go. That is upstream incivilware/telaand out of scope for this repo per CONTRIBUTING. Mentioned so the same symptom reported from the CLI is not mistaken for this fix failing.tela.Updaterperforms no ownership check of its own — I checked. The gate is entirely client-side, so nothing in the TELA library needs to change for this to work.InstallSmartContractalways takes wallet #0 whilegetWalletForDeployment(used byInstallDOC,InstallINDEX,UpdateINDEX,DeployTELABatch) prefers the wallet open in the interface, with nothing on screen saying so. A separate, real inconsistency I ran into while chasing this; not touched here. Happy to open an issue if it is useful.Testing
tela_index_owner_test.gois a table test over: identical renderings (mainnet and simulator), both mainnet/simulator cross-pairs, a different wallet in both networks,"anon", empty strings on either side, an unparseable author, and an integrated address against its base address. The simulator vectors are derived at test time by flippingMainneton a mainnet vector, so there are no hardcodeddeto1constants to rot.Also run locally:
gofmt -l,go vet ./...,go build ./...,go test -short ./...(matching CI).Manually, on a fresh simulator: a full set of DOCs and an INDEX installed, one DOC redeployed with the INDEX SCID baked into it, then the INDEX updated — accepted, broadcast and mined, and the INDEX now serving the new DOC. The same sequence was refused at the client before this change.
AI disclosure
Per CONTRIBUTING: this patch was written with an AI coding agent (Claude). The root cause above was read out of the actual sources —
derohedvm/dvm_functions.go,civilware/telaTELA-INDEX-1.basandtela.go— rather than inferred, and the unit test was compiled and run against the pinnedDHEBP/deroherevision fromgo.mod.Based on
dev@827de4e(v1.0.8).