Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dart/catalyst_cardano_serialization): x509 self-signed certificate #831

Merged
merged 16 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';

import 'package:catalyst_cardano/catalyst_cardano.dart';
import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,6 @@

part of 'main.dart';

final _derCert = X509DerCertificate.fromHex(
'''
308202343082019DA00302010202145A
FC371DAF301793CF0B1835A118C2F903
63D5D9300D06092A864886F70D01010B
05003045310B30090603550406130241
553113301106035504080C0A536F6D65
2D53746174653121301F060355040A0C
18496E7465726E657420576964676974
7320507479204C7464301E170D323430
3731313038353733365A170D32353037
31313038353733365A3045310B300906
03550406130241553113301106035504
080C0A536F6D652D5374617465312130
1F060355040A0C18496E7465726E6574
205769646769747320507479204C7464
30819F300D06092A864886F70D010101
050003818D0030818902818100CD28E2
0B157CA70C85433C1689B1D5890EC479
BDD1FFDCC5647AE12BE9BADF4AF20764
CD24BD64130831A57506DFBBDD3E924C
96B259C6CCEDF24D6A25618F0819643C
739F145B733C3C94333E5937B499ADA9
A4FFC127457C7CB557F2F5623DCADEA1
E06F09129DB9584B0AEE949244B3252B
52AFDE5D385C65E563A6EFB07F020301
0001A321301F301D0603551D0E041604
1492EB169818B833588321957A846077
AA239CF3A0300D06092A864886F70D01
010B0500038181002E5F73333CE667E4
172B252416EAA1D2E9681F59943724B4
F366A8B930443CA6B69B12DD9DEBEE9C
8A6307695EE1884DA4B00136195D1D82
23D1C253FF408EDFC8ED03AF1819244C
35D3843855FB9AF86E84FB7636FA3F4A
0FC396F6FB6FD16D3BCEBDE68A8BD81B
E61E8EE7D77E9F7F9804E03EBC31B458
1313C955A667658B
'''
.replaceAll('\n', ''),
);

final _c509Cert = C509Certificate.fromHex(
'''
004301F50D6B524643207465737420
43411A63B0CD001A6955B90047010123
456789AB01582102B1216AB96E5B3B33
40F5BDF02E693F16213A04525ED44450
B1019C2DFD3838AB010058406FC90301
5259A38C0800A3D0B2969CA21977E8ED
6EC344964D4E1C6B37C8FB541274C3BB
81B2F53073C5F101A5AC2A92886583B6
A2679B6E682D2A26945ED0B2
'''
.replaceAll('\n', ''),
);

const _catalystUserRoleRegistrationPurpose =
'ca7a1457-ef9f-4c7f-9c74-7f8c4a4cfa6c';

Expand All @@ -73,6 +16,7 @@ Future<void> _signAndSubmitRbacTx({
var result = '';
try {
final changeAddress = await api.getChangeAddress();
final rewardAddresses = await api.getRewardAddresses();

final utxos = await api.getUtxos(
amount: const Balance(
Expand All @@ -86,6 +30,7 @@ Future<void> _signAndSubmitRbacTx({

final x509Envelope = await _buildMetadataEnvelope(
utxos: utxos,
rewardAddress: rewardAddresses.first,
);

final auxiliaryData = AuxiliaryData.fromCbor(
Expand All @@ -95,18 +40,25 @@ Future<void> _signAndSubmitRbacTx({
final unsignedTx = _buildUnsignedRbacTx(
inputs: utxos,
changeAddress: changeAddress,
rewardAddress: rewardAddresses.first,
auxiliaryData: auxiliaryData,
);

print('unsigned tx: ${hex.encode(cbor.encode(unsignedTx.toCbor()))}');

final witnessSet = await api.signTx(transaction: unsignedTx);

print('Witness set: $witnessSet');

final signedTx = Transaction(
body: unsignedTx.body,
isValid: true,
witnessSet: witnessSet,
auxiliaryData: unsignedTx.auxiliaryData,
);

print('signed tx: ${hex.encode(cbor.encode(signedTx.toCbor()))}');

final txHash = await api.submitTx(transaction: signedTx);
result = 'Tx hash: ${txHash.toHex()}';
} catch (error) {
Expand All @@ -124,22 +76,29 @@ Future<void> _signAndSubmitRbacTx({

Future<X509MetadataEnvelope<RegistrationData>> _buildMetadataEnvelope({
required List<TransactionUnspentOutput> utxos,
required ShelleyAddress rewardAddress,
}) async {
final keyPair =
await Ed25519KeyPair.fromSeed(Ed25519PrivateKey.seeded(0).bytes);
final seed = List.generate(
Ed25519PrivateKey.length,
(i) => Random().nextInt(256),
);

final keyPair = await Ed25519KeyPair.fromSeed(seed);

final cert = await generateX509Certificate(
keyPair: keyPair,
stakeAddress: rewardAddress,
);

final derCert = cert.toDer();

final x509Envelope = X509MetadataEnvelope.unsigned(
purpose: UuidV4.fromString(_catalystUserRoleRegistrationPurpose),
txInputsHash: TransactionInputsHash.fromTransactionInputs(utxos),
previousTransactionId: _transactionHash,
chunkedData: RegistrationData(
derCerts: [_derCert],
cborCerts: [_c509Cert],
derCerts: [derCert],
publicKeys: [keyPair.publicKey],
revocationSet: [
CertificateHash.fromX509DerCertificate(_derCert),
CertificateHash.fromC509Certificate(_c509Cert),
],
roleDataSet: {
RoleData(
roleNumber: 0,
Expand All @@ -150,7 +109,7 @@ Future<X509MetadataEnvelope<RegistrationData>> _buildMetadataEnvelope({
),
),
roleEncryptionKey: KeyReference(
hash: CertificateHash.fromX509DerCertificate(_derCert),
hash: CertificateHash.fromX509DerCertificate(derCert),
),
paymentKey: 0,
roleSpecificData: {
Expand Down Expand Up @@ -195,13 +154,23 @@ Future<X509MetadataEnvelope<RegistrationData>> _buildMetadataEnvelope({
Transaction _buildUnsignedRbacTx({
required List<TransactionUnspentOutput> inputs,
required ShelleyAddress changeAddress,
required ShelleyAddress rewardAddress,
required AuxiliaryData auxiliaryData,
}) {
final txBuilder = TransactionBuilder(
requiredSigners: {
rewardAddress.publicKeyHash,
},
config: _buildTransactionBuilderConfig(),
inputs: inputs,
networkId: NetworkId.testnet,
auxiliaryData: auxiliaryData,
witnessBuilder: const TransactionWitnessSetBuilder(
vkeys: {},
// TODO(dtscalac): investigate if vkeyCount can be found out
// in a better way, count = reward addresses vkeys + payment address vkey
vkeysCount: 2,
),
);

final txBody = txBuilder.withChangeAddressIfNeeded(changeAddress).buildBody();
Expand All @@ -213,3 +182,44 @@ Transaction _buildUnsignedRbacTx({
auxiliaryData: auxiliaryData,
);
}

Future<X509Certificate> generateX509Certificate({
required Ed25519KeyPair keyPair,
required ShelleyAddress stakeAddress,
}) async {
const maxInt = 4294967296;

/* cSpell:disable */
const issuer = X509DistinguishedName(
countryName: 'US',
stateOrProvinceName: 'California',
localityName: 'San Francisco',
organizationName: 'MyCompany',
organizationalUnitName: 'MyDepartment',
commonName: 'mydomain.com',
);

final tbs = X509TBSCertificate(
serialNumber: Random().nextInt(maxInt),
subjectPublicKey: keyPair.publicKey,
issuer: issuer,
validityNotBefore: DateTime.now().toUtc(),
validityNotAfter: X509TBSCertificate.foreverValid,
subject: issuer,
extensions: X509CertificateExtensions(
subjectAltName: [
'mydomain.com',
'www.mydomain.com',
'example.com',
'www.example.com',
'web+cardano://addr/${stakeAddress.toBech32()}',
],
),
);
/* cSpell:enable */

return X509Certificate.generateSelfSigned(
tbsCertificate: tbs,
keyPair: keyPair,
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export 'src/fees.dart';
export 'src/hashes.dart';
export 'src/rbac/auth_token.dart';
export 'src/rbac/registration_data.dart';
export 'src/rbac/x509_certificate.dart';
export 'src/rbac/x509_metadata_envelope.dart';
export 'src/redeemer.dart';
export 'src/scripts.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import 'package:bip32_ed25519/bip32_ed25519.dart';
import 'package:catalyst_cardano_serialization/src/exceptions.dart';
import 'package:catalyst_cardano_serialization/src/hashes.dart';
import 'package:catalyst_cardano_serialization/src/types.dart';
import 'package:cbor/cbor.dart';
import 'package:equatable/equatable.dart';
Expand Down Expand Up @@ -78,6 +79,11 @@ class ShelleyAddress extends Equatable implements CborEncodable {
/// Returns the [NetworkId] related to this address.
NetworkId get network => _extractNetworkId(bytes);

/// Returns the [Ed25519PublicKeyHash] contained in this address.
///
/// Practically it's the address without the header byte.
Ed25519PublicKeyHash get publicKeyHash => _extractPublicKeyHash(bytes);

/// Encodes the address in bech32 format.
String toBech32() {
final prefix = _computeHrp(network, hrp);
Expand Down Expand Up @@ -150,4 +156,11 @@ class ShelleyAddress extends Equatable implements CborEncodable {
? NetworkId.testnet
: NetworkId.mainnet;
}

/// Extracts the payload from [bytes].
///
/// Format [ 8 bit header | payload ]
static Ed25519PublicKeyHash _extractPublicKeyHash(List<int> bytes) {
return Ed25519PublicKeyHash.fromBytes(bytes: bytes.sublist(1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,17 @@ final class TxValueSizeExceededException extends Equatable

/// Exception thrown when parsing a hash that has incorrect length.
final class HashFormatException extends Equatable implements Exception {
damian-molinski marked this conversation as resolved.
Show resolved Hide resolved
/// Exception details.
final String? message;

/// The default constructor for [HashFormatException].
const HashFormatException();
const HashFormatException([this.message]);

@override
String toString() => 'HashFormatException';
String toString() => 'HashFormatException: $message';

@override
List<Object?> get props => [];
List<Object?> get props => [message];
}

/// Exception thrown if the address doesn't match the bech32 specification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ abstract base class BaseHash extends Equatable implements CborEncodable {
/// Constructs the [BaseHash] from raw [bytes].
BaseHash.fromBytes({required this.bytes}) {
if (bytes.length != length) {
throw const HashFormatException();
throw HashFormatException('Expected $length bytes, got: ${bytes.length}');
}
}

Expand Down
Loading
Loading