verifyCardanoSignature function

VerifyResult verifyCardanoSignature(
  1. VerifyCardanoSignatureArgs args
)

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;
}