Repository navigation
fix(soroban): render signed SCMap entry on the signing screens - #1012
Conversation
|
Android Emulator preview APK is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-f7e11d957e3259ac3602 |
|
iOS Simulator preview build is ready: https://github.com/stellar/freighter-mobile/releases/tag/untagged-f7df808ea508054af58a |
…oNative Decoding an SCMap to a JS object coerced every key through ToPropertyKey and applied last-write-wins, so a map with N signed entries could render as one line on the approval screen. Render maps and vecs from the signed entry list instead, as a value literal where quoting carries the key type. Decode SCString, SCSymbol, executable tags and function names strictly, falling back to the reversible SEP-51 escape form only when the bytes are not valid UTF-8. Two distinct binary payloads can no longer collapse onto one screen string, and valid non-ASCII text stays readable instead of being escaped byte by byte.
…l type Ports the remaining two commits of stellar/freighter#3017. - Escape XdrString-backed fields to one prefix-free byte string: valid text containing `\xff` rendered like the byte 0xFF, and bidi overrides reached the screen raw, letting one signed string reorder into another. - Decode strict UTF-8 by hand -- RN's TextDecoder polyfill throws on the `fatal` option, which Jest never sees because Node implements it. - Render arguments from a token stream, so the drawn and copied text cannot drift, and let the signer tap a scalar to see the arm it was signed as. - scValByType returns a string on every arm; `false`, `void`, a ledger-key instance and a non-wasm instance each reached the screen as an empty row. Instances now render their storage map and the CAP-85 owner and tag. - Key the contract-spec lookup off the raw signed function name, not the escaped display form.
998a16a to
28ebcf7
Compare
| punct("owner: "), | ||
| value(addressToString(ref.executableOwner), executable.type), | ||
| ], | ||
| [punct("tag: "), value(xdrStringToDisplay(ref.tag), executable.type)], |
There was a problem hiding this comment.
An executable tag can fake extra fields, including a second owner
Merge blocker. The signing screen can show an owner address that was never signed.
TL;DR: The external-reference tag is shown on the signing screen without quotes or escaping of commas, colons or brackets. A contract-instance tag of x, owner: G…ATTACKER therefore renders as a second owner: field. The same problem lets a vector holding one tag "1, 2" look identical to a vector of two numbers.
Steps to reproduce:
- Build a contract-call argument containing a contract instance whose executable is an external reference with tag
x, owner: <any G address>. - Open it on the signing screen. It shows:
contractInstance {
executable: externalRef {
owner: GA46Q…ASTJD,
tag: x, owner: GAAOG…CZBQ2
}
}
The compact (map-key) form has the same problem.
Detailed explanation (for agents)
Root cause: in a value literal, both sites render the tag with xdrStringToDisplay, which escapes backslashes and control characters but not the structural characters , : { } [ ]. Strings and symbols already go through the quoting xdrStringToLiteral, but these two sites don't.
executableToTokens, external-reftag:freighter-mobile/src/helpers/soroban.ts
Line 516 in 28ebcf7
scValToDisplayTokens,scvExecutableTag:freighter-mobile/src/helpers/soroban.ts
Lines 573 to 575 in 28ebcf7
The separate tag row that getInvocationArgs builds for create-contract (externalRefDetails.tag) is its own labelled field, not part of a literal, so it can't fake structure this way.
Deterministic repro: call scValToDisplayValue on scvContractInstance with contractExecutableExternalRef({ executableOwner, tag: "x, owner: G…" }), and on scvMap with keys scvVec([scvExecutableTag("1, 2")]) and scvVec([scvU32(1), scvU32(2)]). The second case renders both keys as [1, 2]. Checked at 28ebcf71108beba44d1c0211f9d2147a9fc914dc against @stellar/stellar-sdk 17.0.1.
Suggested fix: render both sites with xdrStringToLiteral(…, "string") so the tag is quoted, with " escaped. If the tag needs to look different from a string, use a wrapper such as tag("…").
There was a problem hiding this comment.
Including this one for the same reason: small fix for correctness
piyalbasu
left a comment
There was a problem hiding this comment.
Left 2 small comments, but otherwise LGTM
Port of stellar/freighter@78c6a04. An external reference's tag is now a quoted string literal and a standalone SCVal tag renders as tag("…"), so a deployer-chosen tag can neither forge an `owner:` field nor read as a string with the same characters.
Summary
Renders Soroban contract-call arguments on the signing screens from the signed
SCValrather than from a decoded native value, so the screen matches what is being signed. Three defects are fixed:SCMapwith N signed entries could render as one line — with no glyph and no warning. Maps now render from the signed entry list, so entry count is preserved by construction.u64(1)andstring("1"), orsymbol("a")andstring("a"), previously rendered as identical keys. Quoting now carries the type: strings quoted, symbols and numbers bare, structured keys inline.SCString,SCSymboland function names are decoded strictly, falling back to the reversible SEP-51 escape form only when the bytes are not valid UTF-8. Two distinct non-UTF-8 payloads can no longer collapse onto the sameU+FFFDstring, and legitimate non-ASCII text is no longer escaped byte by byte (caférenderedcaf\xc3\xa9).All three hold at every nesting depth. Only a top-level argument was handled before: one level down a map collapsed, a non-UTF-8 string rendered as
{"0":97,"1":108,…}, and bytes as{"type":"Buffer","data":[…]}.Affected surfaces:
SignTransactionDetails(operations and authorizations) and the WalletKit dApp auth-entry display, both of which route through the shared helpers.Display format change — map and vector parameters render as a value literal rather than JSON:
Non-UTF-8 strings and symbols also move from bare hex to the SEP-51 form already used for executable tags:
fffebecomes\xff\xfe.Test plan
yarn jest— 3283 passing, including 17 new cases: entry-count preservation for structured and mixed-type map keys, address- andu32-keyed controls, nested containers, and strict decoding of non-UTF-8 strings, symbols and function namesyarn lint:tsandeslintclean