psbt: return an error if fetching input_amount when not present
What changed, and why it matters
This commit changes a library function so that it now returns an error when a PSBT/PSET input amount is missing, instead of silently returning zero. It also adds a new 'has_input_amount' check so callers can ask whether the value exists before reading it. The change is an intentional fix to prevent callers from accidentally treating a missing amount as zero. It also cleans up some old, non-existent function names in the Java wrapper.
Treat this as a hardening/behavior-correction patch. Downstream projects using wally_psbt_get_input_amount() should update to check the return code or use psbt_has_input_amount() before fetching the value, because the getter now fails when the amount is absent. Review any code that assumed zero meant 'missing'.
Security signals we found
API behavior change: missing field now returns error instead of default zero
Adds presence-check accessor to prevent callers from reading unset values
ABI break noted by vendor
Pattern matches existing output_amount safety design
No explicit CVE or security advisory referenced in commit
Evidence from the diff
The patch modifies wally_psbt_get_input_amount() in src/psbt.c so it returns WALLY_EINVAL when the PSBT version is not 2 or when the input’s has_amount flag is false. Previously the generated PSBT_GET_I_PSET macro likely returned WALLY_OK with *written set to 0 when the field was absent. A new wally_psbt_has_input_amount() accessor is added, mirroring the existing output_amount pattern. Bindings for Python, Java, WASM and test utilities are updated accordingly, and non-existent input_value accessors are removed from the SWIG Java wrapper. The commit message explicitly notes this is an ABI change.
Changed components
src/psbt.c PSBT/PSET input amount getterinclude/wally_psbt_members.h public APIPython SWIG bindings (src/swig_python/contrib/psbt.py)Java SWIG wrapper (src/swig_java/swig.i)WASM bindings (src/wasm_package/src/functions.js, index.d.ts, tools/wasm_exports.sh)test utilities (src/test/util.py)Inspect captured patch +44 / −10
### include/wally_psbt_members.h
@@ -142,6 +142,7 @@ WALLY_CORE_API int wally_psbt_get_input_utxo_rangeproof_len(const struct wally_p
WALLY_CORE_API int wally_psbt_set_input_amount(struct wally_psbt *psbt, size_t index, uint64_t amount);
WALLY_CORE_API int wally_psbt_clear_input_amount(struct wally_psbt *psbt, size_t index);
+WALLY_CORE_API int wally_psbt_has_input_amount(const struct wally_psbt *psbt, size_t index, size_t *written);
WALLY_CORE_API int wally_psbt_set_input_amount_rangeproof(struct wally_psbt *psbt, size_t index, const unsigned char *rangeproof, size_t rangeproof_len);
WALLY_CORE_API int wally_psbt_clear_input_amount_rangeproof(struct wally_psbt *psbt, size_t index);
WALLY_CORE_API int wally_psbt_set_input_asset(struct wally_psbt *psbt, size_t index, const unsigned char *asset, size_t asset_len);
### src/psbt.c
@@ -5932,7 +5932,35 @@ int wally_psbt_clear_input_required_lockheight(struct wally_psbt *psbt, size_t i
PSBT_FIELD(output, taproot_internal_key, PSBT_0)
#ifndef WALLY_ABI_NO_ELEMENTS
+#ifndef BUILD_ELEMENTS
PSBT_GET_I_PSET(input, amount, uint64_t, PSBT_2)
+int wally_psbt_has_input_amount(const struct wally_psbt *psbt, size_t index, size_t* written)
+{
+ if (written)
+ *written = 0;
+ return WALLY_EINVAL;
+}
+#else
+int wally_psbt_get_input_amount(const struct wally_psbt *psbt, size_t index,
+ uint64_t *written)
+{
+ struct wally_psbt_input *p = psbt_get_input(psbt, index);
+ if (written) *written = 0;
+ if (!p || !written || psbt->version != PSBT_2 || !p->has_amount)
+ return WALLY_EINVAL;
+ *written = p->amount;
+ return WALLY_OK;
+}
+int wally_psbt_has_input_amount(const struct wally_psbt *psbt, size_t index, size_t* written)
+{
+ struct wally_psbt_input *p = psbt_get_input(psbt, index);
+ if (written) *written = 0;
+ if (!p || !written || psbt->version != PSBT_2)
+ return WALLY_EINVAL;
+ *written = p->has_amount ? 1 : 0;
+ return WALLY_OK;
+}
+#endif
int wally_psbt_clear_input_amount(struct wally_psbt *psbt, size_t index) {
if (!psbt || psbt->version != PSBT_2) return WALLY_EINVAL;
return wally_psbt_input_clear_amount(psbt_get_input(psbt, index));
### src/swig_java/swig.i
@@ -723,7 +723,6 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
%returns_void__(wally_psbt_clear_input_required_locktime);
%returns_void__(wally_psbt_clear_input_sequence);
%returns_void__(wally_psbt_clear_input_utxo_rangeproof);
-%returns_void__(wally_psbt_clear_input_value);
%returns_void__(wally_psbt_clear_output_amount);
%returns_void__(wally_psbt_clear_output_asset);
%returns_void__(wally_psbt_clear_output_asset_blinding_surjectionproof);
@@ -835,7 +834,6 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
%rename("psbt_get_input_utxo") wally_psbt_get_input_utxo_alloc;
%returns_size_t(wally_psbt_get_input_utxo_rangeproof);
%returns_size_t(wally_psbt_get_input_utxo_rangeproof_len);
-%returns_uint64(wally_psbt_get_input_value);
%returns_size_t(wally_psbt_get_input_witness_script);
%returns_size_t(wally_psbt_get_input_witness_script_len);
%returns_struct(wally_psbt_get_input_witness_utxo_alloc, wally_tx_output);
@@ -887,7 +885,7 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
%returns_size_t(wally_psbt_has_global_genesis_blockhash);
%returns_size_t(wally_psbt_has_input_required_lockheight);
%returns_size_t(wally_psbt_has_input_required_locktime);
-%returns_size_t(wally_psbt_has_input_value);
+%returns_size_t(wally_psbt_has_input_amount);
%returns_size_t(wally_psbt_has_output_amount);
%returns_size_t(wally_psbt_has_output_asset);
%returns_size_t(wally_psbt_has_output_asset_blinding_surjectionproof);
@@ -945,7 +943,6 @@ static jobjectArray create_jstringArray(JNIEnv *jenv, char **p, size_t len) {
%returns_void__(wally_psbt_set_input_unknowns);
%returns_void__(wally_psbt_set_input_utxo);
%returns_void__(wally_psbt_set_input_utxo_rangeproof);
-%returns_void__(wally_psbt_set_input_value);
%returns_void__(wally_psbt_set_input_witness_script);
%returns_void__(wally_psbt_set_input_witness_utxo);
%returns_void__(wally_psbt_set_input_witness_utxo_from_tx);
### src/swig_python/contrib/psbt.py
@@ -586,14 +586,18 @@ def test_psbt(self):
#
if is_elements_build():
# PSET: Explicit amount/issuance amount/inflation keys/pegin amount
- for setfn, getfn in [
- (psbt_set_input_amount, psbt_get_input_amount),
- (psbt_set_input_issuance_amount, psbt_get_input_issuance_amount),
- (psbt_set_input_inflation_keys, psbt_get_input_inflation_keys),
- (psbt_set_input_pegin_amount, psbt_get_input_pegin_amount)]:
+ for setfn, clearfn, getfn, hasfn in [
+ (psbt_set_input_amount, psbt_clear_input_amount,
+ psbt_get_input_amount, psbt_has_input_amount),
+ (psbt_set_input_issuance_amount, None,
+ psbt_get_input_issuance_amount, None),
+ (psbt_set_input_inflation_keys, None,
+ psbt_get_input_inflation_keys, None),
+ (psbt_set_input_pegin_amount, None,
+ psbt_get_input_pegin_amount, None)]:
self._throws(setfn, psbt, 0, 1234) # Non v2 PSBT
self._throws(getfn, psbt, 0) # Non v2 PSBT
- self._try_get_set_i(setfn, None, getfn, None, pset2, 1234)
+ self._try_get_set_i(setfn, clearfn, getfn, hasfn, pset2, 1234)
# Explicit amount
self._throws(psbt_clear_input_amount, psbt, 0) # Non v2 PSBT
### src/test/util.py
@@ -917,6 +917,7 @@ class wally_psbt(Structure):
('wally_psbt_get_tx_modifiable_flags', c_int, [POINTER(wally_psbt), c_size_t_p]),
('wally_psbt_get_version', c_int, [POINTER(wally_psbt), c_size_t_p]),
('wally_psbt_has_fallback_locktime', c_int, [POINTER(wally_psbt), c_size_t_p]),
+ ('wally_psbt_has_input_amount', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]),
('wally_psbt_has_input_required_lockheight', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]),
('wally_psbt_has_input_required_locktime', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]),
('wally_psbt_has_output_amount', c_int, [POINTER(wally_psbt), c_size_t, c_size_t_p]),
### src/wasm_package/src/functions.js
@@ -416,6 +416,7 @@ export const psbt_get_tx_version = wrap('wally_psbt_get_tx_version', [T.OpaqueRe
export const psbt_get_version = wrap('wally_psbt_get_version', [T.OpaqueRef, T.DestPtr(T.Int32)]);
export const psbt_has_fallback_locktime = wrap('wally_psbt_has_fallback_locktime', [T.OpaqueRef, T.DestPtr(T.Int32)]);
export const psbt_has_global_genesis_blockhash = wrap('wally_psbt_has_global_genesis_blockhash', [T.OpaqueRef, T.DestPtr(T.Int32)]);
+export const psbt_has_input_amount = wrap('wally_psbt_has_input_amount', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]);
export const psbt_has_input_required_lockheight = wrap('wally_psbt_has_input_required_lockheight', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]);
export const psbt_has_input_required_locktime = wrap('wally_psbt_has_input_required_locktime', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]);
export const psbt_has_output_amount = wrap('wally_psbt_has_output_amount', [T.OpaqueRef, T.Int32, T.DestPtr(T.Int32)]);
### src/wasm_package/src/index.d.ts
@@ -376,6 +376,7 @@ export function psbt_get_tx_version(psbt: Ref_wally_psbt): number;
export function psbt_get_version(psbt: Ref_wally_psbt): number;
export function psbt_has_fallback_locktime(psbt: Ref_wally_psbt): number;
export function psbt_has_global_genesis_blockhash(psbt: Ref_wally_psbt): number;
+export function psbt_has_input_amount(psbt: Ref_wally_psbt, index: number): number;
export function psbt_has_input_required_lockheight(psbt: Ref_wally_psbt, index: number): number;
export function psbt_has_input_required_locktime(psbt: Ref_wally_psbt, index: number): number;
export function psbt_has_output_amount(psbt: Ref_wally_psbt, index: number): number;
### tools/wasm_exports.sh
@@ -638,6 +638,7 @@ if [ -z "$DISABLE_ELEMENTS" ]; then
,'_wally_psbt_get_output_value_rangeproof_len' \
,'_wally_psbt_get_pset_modifiable_flags' \
,'_wally_psbt_has_global_genesis_blockhash' \
+,'_wally_psbt_has_input_amount' \
,'_wally_psbt_has_output_blinder_index' \
,'_wally_psbt_input_clear_amount_rangeproof' \
,'_wally_psbt_input_clear_asset' \Why this scored 40/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.