verifyXrpSignature function
XRP has no request id — this check IS the binding. The signed binary is
split into its canonical fields; TxnSignature is removed; the remainder
(prefixed with the XRPL signing tag STX\0) is hashed with SHA-512-half
and the DER signature is verified against SigningPubKey, which must also
equal the key your request carried.
The field walker covers the types Payment-class transactions use; a
transaction carrying an exotic field type comes back checked: false
rather than a false verdict.
Implementation
VerifyResult verifyXrpSignature(VerifyXrpSignatureArgs args) {
List<_XrpField> fields;
try {
fields = _splitFields(args.signedTx);
} on _WalkError catch (e) {
if (e.message == 'unsupported-field') {
return unverifiable(
'the transaction carries a field type this checker does not walk');
}
return failed('the signed transaction is not readable: ${e.message}');
}
_XrpField? signingPubKey;
_XrpField? txnSignature;
for (final f in fields) {
if (f.header == 0x73) signingPubKey ??= f;
if (f.header == 0x74) txnSignature ??= f;
}
if (signingPubKey == null || txnSignature == null) {
return failed(
'the signed transaction is missing SigningPubKey or TxnSignature');
}
final expected = hexToBytes(args.expectedSigningPubKey);
if (!equalBytes(signingPubKey.value, expected)) {
return failed(
'the transaction was signed with a different key (${bytesToHex(signingPubKey.value)})',
);
}
// Signing payload: every field except TxnSignature, in the original
// (canonical) order, behind the 'STX\0' prefix; SHA-512 halved.
final payload = concatBytes([
Uint8List.fromList([0x53, 0x54, 0x58, 0x00]),
for (final f in fields)
if (f.header != 0x74) f.raw,
]);
final digest = Uint8List.sublistView(sha512(payload), 0, 32);
bool ok;
try {
ok = Secp256k1.verify(_derToCompact(txnSignature.value), digest, expected);
} on Object catch (e) {
return failed('XRP signature could not be checked: $e');
}
return ok
? verified
: failed('the signature does not verify against SigningPubKey');
}