verifyCardanoSignature function
Recompute the digest the device signs — BLAKE2b-256 of the ENCODED FIRST
ELEMENT of the transaction CBOR array (the tx body) — and verify every
[vkey, signature] pair against it. With account + signerPaths, the
vkeys are additionally required to be exactly the soft-derived children of
YOUR linked account at the request's own paths.
Implementation
VerifyResult verifyCardanoSignature(VerifyCardanoSignatureArgs args) {
List<CardanoWitness> witnesses;
try {
witnesses = args.witnesses ??
(args.witnessSet != null
? parseWitnessSet(args.witnessSet!)
: <CardanoWitness>[]);
} on EraSdkError catch (e) {
return failed('witness set is not readable: ${e.message}');
}
if (witnesses.isEmpty) return failed('no witnesses to verify');
Uint8List digest;
try {
digest = blake2b256(firstArrayItemBytes(args.signData));
} on FormatException catch (e) {
return failed('signData is not a readable transaction array: ${e.message}');
}
for (final witness in witnesses) {
bool ok;
try {
ok = ed25519Verify(witness.vkey, digest, witness.signature);
} on Object catch (e) {
return failed('Cardano signature could not be checked: $e');
}
if (!ok) {
return failed('a witness signature does not verify against its own vkey');
}
}
final account = args.account;
final signerPaths = args.signerPaths;
if (account != null && signerPaths != null && signerPaths.isNotEmpty) {
final accountLevels = parsePath(account.accountPath);
final expected = <String, String>{}; // vkey hex -> path
for (final path in <String>{...signerPaths}) {
final levels = parsePath(path);
var extendsAccount = levels.length == accountLevels.length + 2;
if (extendsAccount) {
for (var i = 0; i < accountLevels.length; i++) {
if (levels[i].index != accountLevels[i].index ||
levels[i].hardened != accountLevels[i].hardened) {
extendsAccount = false;
break;
}
}
}
final tail = levels.sublist(accountLevels.length);
if (!extendsAccount || tail.any((l) => l.hardened)) {
return failed(
'signer path $path does not extend the account path with two soft components',
);
}
final vkey = cardanoSoftDerivePath(
account.publicKey,
account.chainCode,
tail.map((l) => l.index).toList(),
);
expected[bytesToHex(vkey)] = path;
}
// Every requested path must have produced a witness…
for (final entry in expected.entries) {
if (!witnesses.any((w) => bytesToHex(w.vkey) == entry.key)) {
return failed(
'no witness for the requested signer path ${entry.value}');
}
}
// …and every witness must belong to a requested path (no foreign keys).
for (final witness in witnesses) {
if (!expected.containsKey(bytesToHex(witness.vkey))) {
return failed(
'the witness set carries a key your request did not ask for');
}
}
}
return verified;
}