signData method
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(),
);
}