signTransactionsBundle method

  1. @SquadronMethod()
  2. @txSignedBundleMarshaler
Future<TxSignedBundle> signTransactionsBundle(
  1. @walletMarshaler CardanoWallet wallet,
  2. @txSigningBundleMarshaler TxSigningBundle bundle,
  3. int deriveMaxAddressCount
)

Implementation

@SquadronMethod()
@txSignedBundleMarshaler
Future<TxSignedBundle> signTransactionsBundle(
  @walletMarshaler CardanoWallet wallet,
  @txSigningBundleMarshaler TxSigningBundle bundle,
  int deriveMaxAddressCount,
) async {
  final txs = bundle.txsData;

  final HdWallet hdWallet = (wallet as CardanoWalletImpl).hdWallet;
  final networkId = wallet.networkId;

  final List<Set<Bip32KeyPair>> signAddressesAll = List.generate(txs.length, (index) => {});
  final List<Set<String>> modifiableSpendCredentialsAll = List.generate(
    txs.length,
    (index) => Set.of(
      txs[index] //
          .signingAddressesRequired
          .map((e) => CardanoAddress.fromBech32OrBase58(e).credentials),
    ),
  );

  final modifiablePubKeyNativeScriptCredentialsAll = List.generate(
    txs.length,
    (index) => _credsFromNativeScripts(txs[index].tx.witnessSet.nativeScripts).toSet(),
  );

  final modifiableExtraSignersCredentialsAll = List.generate(
    txs.length,
    (index) => Set.of(txs[index].tx.body.requiredSigners?.signersBytes.map((e) => e.hexEncode()) ?? <String>[]),
  );

  // check for stake credentials/full hex stake address ; remove if we find any
  final extraSignersRequestedStakeAddressAll = modifiableExtraSignersCredentialsAll
      .map(
        (e) => e.removeFirstWhere((element) => element.contains(wallet.stakeAddress.credentials)),
      )
      .toList(growable: false);

  final pubKeyNativeScriptRequestedStakeAddressAll = modifiablePubKeyNativeScriptCredentialsAll
      .map(
        (e) => e.removeFirstWhere((element) => element.contains(wallet.stakeAddress.credentials)),
      )
      .toList(growable: false);

  final hasCertsForWalletStakeKeyAll = txs
      .map((e) => e.tx)
      .map((tx) => tx.body.certs.requiresStakeSignature(wallet.stakeAddress.credentialsBytes))
      .toList(growable: false);
  final hasCertsForColdKeyAll = txs
      .map((e) => e.tx)
      .map((tx) => tx.body.certs.requiresCommitteeColdSignature(
            wallet.constitutionalCommiteeCold.value.credentialsBytes,
          ))
      .toList(growable: false);

  final hasCertsOrVotesForDRepAll = txs
      .map((e) => e.tx)
      .map(
        (tx) =>
            tx.body.certs.requiresDrepSignature(wallet.drepId.value.credentialsBytes) ||
            tx.body.votingProcedures.requiresDrepSignature(wallet.drepId.value.credentialsBytes),
      )
      .toList(growable: false);
  final hasCommitteeHotVotesAll = txs
      .map((e) => e.tx)
      .map(
        (tx) => tx.body.votingProcedures.requiresCommitteeHotSignature(
          wallet.constitutionalCommiteeHot.value.credentialsBytes,
        ),
      )
      .toList(growable: false);
  final hasWithdrawalForWalletStakeKeyAll = txs
      .map((e) => e.tx)
      .map(
        (tx) =>
            tx.body.withdrawals?.any(
              (element) => element.stakeAddressBech32.bech32ToHex().endsWith(wallet.stakeAddress.credentials),
            ) ??
            false,
      )
      .toList(growable: false);

  final deriveAddressWorker = WalletTasksWorkerPool(
    concurrencySettings: const ConcurrencySettings(maxParallel: 2, maxWorkers: 2, minWorkers: 2),
  );

  try {
    await deriveAddressWorker.start();

    for (int i = 0; i < deriveMaxAddressCount; i++) {
      final paymentAddrForIndexFuture = deriveAddressWorker.execute(
        (worker) => worker.deriveAddressKit(wallet.hdWallet, networkId, i, Bip32KeyRole.payment),
      );
      final changeAddrForIndexFuture = deriveAddressWorker.execute(
        (worker) => worker.deriveAddressKit(wallet.hdWallet, networkId, i, Bip32KeyRole.change),
      );
      final paymentAddrForIndex = await paymentAddrForIndexFuture;
      final changeAddrForIndex = await changeAddrForIndexFuture;
      final hexPaymentAddrForIndex = paymentAddrForIndex.address.hexEncoded;
      final hexChangeAddrForIndex = changeAddrForIndex.address.hexEncoded;
      final bech32PaymentCredsForIndex = paymentAddrForIndex.address.credentials;
      final bech32ChangeCredsForIndex = changeAddrForIndex.address.credentials;

      modifiableSpendCredentialsAll.forEachIndexed((index, e) {
        if (e.remove(bech32PaymentCredsForIndex)) {
          signAddressesAll[index].add(paymentAddrForIndex);
        }
        if (e.remove(bech32ChangeCredsForIndex)) {
          signAddressesAll[index].add(changeAddrForIndex);
        }
      });

      modifiableExtraSignersCredentialsAll.forEachIndexed((index, e) {
        if (e.remove(bech32PaymentCredsForIndex)) {
          signAddressesAll[index].add(paymentAddrForIndex);
        }
        if (e.remove(bech32ChangeCredsForIndex)) {
          signAddressesAll[index].add(changeAddrForIndex);
        }
      });

      modifiablePubKeyNativeScriptCredentialsAll.forEachIndexed((index, e) {
        if (e.removeFirstWhere(hexPaymentAddrForIndex.contains)) {
          signAddressesAll[index].add(paymentAddrForIndex);
        }

        if (e.removeFirstWhere(hexChangeAddrForIndex.contains)) {
          signAddressesAll[index].add(changeAddrForIndex);
        }
      });

      if (modifiableSpendCredentialsAll.every((e) => e.isEmpty) &&
          modifiableExtraSignersCredentialsAll.every((e) => e.isEmpty) &&
          modifiablePubKeyNativeScriptCredentialsAll.every((e) => e.isEmpty)) {
        break;
      }
    }
  } finally {
    deriveAddressWorker.stop();
  }

  if (modifiableSpendCredentialsAll.flattened.isNotEmpty) {
    throw SigningAddressNotFoundException(
      missingAddresses: modifiableSpendCredentialsAll.flattened.toSet(),
      searchedAddressesCount: deriveMaxAddressCount,
    );
  }

  final needsStakeAddrSigningAll = List.generate(
    txs.length,
    (index) =>
        hasCertsForWalletStakeKeyAll[index] ||
        hasWithdrawalForWalletStakeKeyAll[index] ||
        extraSignersRequestedStakeAddressAll[index] ||
        pubKeyNativeScriptRequestedStakeAddressAll[index],
  );

  needsStakeAddrSigningAll.forEachIndexed((index, needsStakeAddrSigning) {
    if (needsStakeAddrSigning) {
      signAddressesAll[index].add(hdWallet.stakeKeys.value);
    }
  });

  final needsConstitutionalCommitteeColdSignAll = List.generate(
    txs.length,
    (index) => hasCertsForColdKeyAll[index],
  );

  needsConstitutionalCommitteeColdSignAll.forEachIndexed((index, needsCommitteeColdSigning) {
    if (needsCommitteeColdSigning) {
      signAddressesAll[index].add(hdWallet.constitutionalCommitteeColdKeys.value);
    }
  });

  final needsConstitutionalCommitteeHotSignAll = List.generate(
    txs.length,
    (index) => hasCommitteeHotVotesAll[index],
  );

  needsConstitutionalCommitteeHotSignAll.forEachIndexed((index, needsCommitteeHotSigning) {
    if (needsCommitteeHotSigning) {
      signAddressesAll[index].add(hdWallet.constitutionalCommitteeHotKeys.value);
    }
  });

  final needsDRepIdSignAll = List.generate(
    txs.length,
    (index) => hasCertsOrVotesForDRepAll[index],
  );

  needsDRepIdSignAll.forEachIndexed((index, needsDRepIdSigning) {
    if (needsDRepIdSigning) {
      signAddressesAll[index].add(hdWallet.drepCredentialKeys.value);
    }
  });

  final txsAndSignatures = txs.mapIndexed((index, e) {
    final tx = e.tx;
    final Uint8List bodyHash = tx.body.blake2bHash256Hex().hexDecode();
    final vKeyWitnesses = signAddressesAll[index].map((signAddr) {
      final signedMessage = signAddr.signingKey.sign(bodyHash);

      final witness = WitnessVKey(
        vkey: signAddr.verifyKey.rawKey.toUint8List(),
        signature: signedMessage.signature.toUint8List(),
      );

      return witness;
    });

    final witness = WitnessSet(
      ivkeyWitnesses: ListWithCborType(
        vKeyWitnesses.toList(),
        CborLengthType.auto,
        null,
      ),
    );

    return TxAndSignature(
      tx: tx,
      txDiff: e.txDiff,
      utxosBeforeTx: e.utxosBeforeTx,
      signingAddressesRequired: e.signingAddressesRequired,
      nweSignatures: witness,
    );
  });

  return TxSignedBundle(
    txsData: txsAndSignatures.toList(growable: false),
    totalDiff: bundle.totalDiff,
    networkId: bundle.networkId,
    receiveAddressBech32: bundle.receiveAddressBech32,
  );
}