signData method

  1. @SquadronMethod()
  2. @dataSignatureMarshaler
Future<DataSignature> signData(
  1. @walletMarshaler CardanoWallet wallet,
  2. String payloadHex,
  3. String requestedSignerRaw,
  4. int deriveMaxAddressCount,
)

Implementation

@SquadronMethod()
@dataSignatureMarshaler
Future<DataSignature> signData(
  @walletMarshaler CardanoWallet wallet,
  String payloadHex,
  String requestedSignerRaw,
  int deriveMaxAddressCount,
) async {
  // if it's a bech32, convert it to hex
  final requestedSignerHex = ["addr", "stake", "drep", "cc_hot", "cc_cold"].any(requestedSignerRaw.startsWith)
      ? requestedSignerRaw.bech32ToHex()
      : requestedSignerRaw;

  final hdWallet = (wallet as CardanoWalletImpl).hdWallet;
  final networkId = wallet.networkId;
  final Uint8List payloadBytes = payloadHex.hexDecode();

  ({Uint8List? keyId, ByteList requestedSigningAddressBytes, Bip32KeyPair signingKeyPair}) dataFromAddress(
      CardanoAddress requestedSigningAddress) {
    final Bip32KeyPair signingKeyPair = switch (requestedSigningAddress.addressType) {
      AddressType.reward => () {
          if (requestedSigningAddress.hexEncoded == wallet.stakeAddress.hexEncoded) {
            return hdWallet.stakeKeys.value;
          } else {
            throw SigningAddressNotFoundException(
              missingAddresses: {requestedSigningAddress.bech32Encoded},
              searchedAddressesCount: 1,
            );
          }
        }(),
      AddressType.base => () {
          for (int i = 0; i < deriveMaxAddressCount; i++) {
            final paymentAddrForIndex = hdWallet.deriveBaseAddressKit(
              index: i,
              role: Bip32KeyRole.payment,
              networkId: networkId,
            );
            final changeAddrForIndex = hdWallet.deriveBaseAddressKit(
              index: i,
              role: Bip32KeyRole.change,
              networkId: networkId,
            );
            if (paymentAddrForIndex.address == requestedSigningAddress) {
              return paymentAddrForIndex;
            } else if (changeAddrForIndex.address == requestedSigningAddress) {
              return changeAddrForIndex;
            }
          }

          // if not found in for loop, throw
          throw SigningAddressNotFoundException(
            missingAddresses: {requestedSigningAddress.bech32Encoded},
            searchedAddressesCount: deriveMaxAddressCount,
          );
        }(),
      AddressType.pointer ||
      AddressType.enterprise ||
      AddressType.byron =>
        throw UnexpectedSigningAddressTypeException(
          hexAddress: requestedSignerRaw,
          type: requestedSigningAddress.addressType,
          signingContext: "When signing payload message",
        ),
    };

    return (
      // not sure when/if we ever need keyId
      // keyId: signingKeyPair.verifyKey.rawKey.toUint8List(), // raw key (no hashing)
      keyId: null,
      requestedSigningAddressBytes: requestedSigningAddress.bytes, // type and network header + hashed key
      signingKeyPair: signingKeyPair, // pub+priv keys for signing
    );
  }

  ({Uint8List? keyId, ByteList requestedSigningAddressBytes, Bip32KeyPair signingKeyPair}) dataFromDrepIdOrCreds(
      String drepIdOrCredsHex) {
    final walletDrepCredentials = wallet.drepId.value.credentialsHex;
    if (!drepIdOrCredsHex.endsWith(walletDrepCredentials)) {
      throw SigningAddressNotFoundException(
        missingAddresses: {requestedSignerRaw},
        searchedAddressesCount: 1,
      );
    }

    final Bip32KeyPair signingKeyPair = hdWallet.drepCredentialKeys.value;
    return (
      // not sure when/if we ever need keyId
      // keyId: signingKeyPair.verifyKey.rawKey.toUint8List(), // (drep) key with no hashing
      keyId: null,
      requestedSigningAddressBytes: ByteList(wallet.drepId.value.credentialsBytes), // hashed key WITHOUT HEADER
      signingKeyPair: signingKeyPair, // pub+priv keys for signing
    );
  }

  final data = switch (requestedSignerHex.length) {
    // This is used for dRep (CIP-95)
    //
    // NOTE: In the future, we can maybe also check against any other payment/change/stake/cc credentials
    //   (since the 56 bytes creds do not include the header which tells us the creds type)
    56 => dataFromDrepIdOrCreds(requestedSignerHex),
    // 58 or 114 is the length of the stake or receive address hex
    58 => () {
        final requestedSignerBytes = requestedSignerHex.hexDecode();
        final headerBytes = requestedSignerBytes[0];
        return headerBytes & 0x0f > 1
            ? dataFromDrepIdOrCreds(requestedSignerHex)
            : dataFromAddress(CardanoAddress.fromHexString(requestedSignerHex));
      }(),
    114 => dataFromAddress(CardanoAddress.fromHexString(requestedSignerHex)),
    _ => throw SigningAddressNotValidException(
        hexInvalidAddressOrCredential: requestedSignerHex,
        signingContext: "When signing payload message",
      )
  };

  final headers = CoseHeaders(
    protectedHeader: CoseProtectedHeaderMap(
      bytes: CoseHeaderMap(
        algorithmId: const CborSmallInt(ALG_EdDSA),
        keyId: data.keyId,
        otherHeaders: CborMap.of({
          CborString(ADDRESS_KEY): CborBytes(data.requestedSigningAddressBytes),
        }),
      ).serializeAsBytes(),
    ),
    unprotectedHeader: CoseHeaderMap(hashed: false, otherHeaders: CborMap.of({})),
  );

  final sigStructure = CoseSigStructure.fromSign1(
    bodyProtected: headers.protectedHeader,
    payload: payloadBytes,
  );
  final dataToSign = sigStructure.serializeAsBytes();

  final SignedMessage signedMessage = data.signingKeyPair.signingKey.sign(dataToSign);

  final coseSign1 = CoseSign1(
    headers: headers,
    payload: payloadBytes,
    signature: signedMessage.signature.toUint8List(),
  );

  final coseKey = CoseKey(keyId: data.signingKeyPair.verifyKey.rawKey.toUint8List());

  return DataSignature(
    coseKeyHex: coseKey.serializeAsBytes().hexEncode(),
    coseSignHex: coseSign1.serializeAsBytes().hexEncode(),
  );
}