Skip to content
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
Expand Up @@ -7,6 +7,7 @@ export 'crypto/crypto_service.dart';
export 'crypto/key_derivation.dart';
export 'crypto/local_crypto_service.dart';
export 'dependency/dependency_provider.dart';
export 'document/document_manager.dart';
export 'document/extension/document_list_sort_ext.dart';
export 'document/extension/document_map_to_list_ext.dart';
export 'document/identifiable.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'dart:typed_data';

import 'package:catalyst_compression/catalyst_compression.dart';
import 'package:catalyst_cose/catalyst_cose.dart';
import 'package:catalyst_key_derivation/catalyst_key_derivation.dart';
import 'package:cbor/cbor.dart';
import 'package:equatable/equatable.dart';

part 'document_manager_impl.dart';

/// Parses the document from the bytes obtained from [BinaryDocument.toBytes].
///
/// Usually this would convert the [bytes] into a [String],
/// decode a [String] into a json and then parse the data class
/// from the json representation.
typedef DocumentParser<T extends BinaryDocument> = T Function(Uint8List bytes);

/// Manages the [SignedDocument]s.
abstract interface class DocumentManager {
/// The default constructor for the [DocumentManager],
/// provides the default implementation of the interface.
const factory DocumentManager() = _DocumentManagerImpl;

/// Parses the document from the [bytes] representation.
///
/// The [parser] must be able to parse the document
/// from the bytes produced by [BinaryDocument.toBytes].
///
/// The implementation of this method must be able to understand the [bytes]
/// that are obtained from the [SignedDocument.toBytes] method.
Future<SignedDocument<T>> parseDocument<T extends BinaryDocument>(
Uint8List bytes, {
required DocumentParser<T> parser,
});

/// Signs the [document] with a single [privateKey].
///
/// The [publicKey] will be added as metadata in the signed document
/// so that it's easier to identify who signed it.
Future<SignedDocument<T>> signDocument<T extends BinaryDocument>(
T document, {
required Uint8List publicKey,
required Uint8List privateKey,
});
}

/// Represents an abstract document that is protected
/// with cryptographic signature.
///
/// The [document] payload can be UTF-8 encoded bytes, a binary data
/// or anything else that can be represented in binary format.
abstract interface class SignedDocument<T extends BinaryDocument> {
/// The default constructor for the [SignedDocument].
const SignedDocument();

/// A getter that returns a parsed document.
T get document;

/// Verifies if the [document] has been signed by a private key
/// that belongs to the given [publicKey].
Future<bool> verifySignature(Uint8List publicKey);

/// Converts the document into binary representation.
Uint8List toBytes();
}

/// Represents an abstract document that can be represented in binary format.
// ignore: one_member_abstracts
abstract interface class BinaryDocument {
/// Converts the document into a binary representation.
///
/// See [DocumentParser].
Uint8List toBytes();

/// Returns the document content type.
DocumentContentType get contentType;
}

/// Defines the content type of the [BinaryDocument].
enum DocumentContentType {
/// The document's content type is JSON.
json,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
part of 'document_manager.dart';

const _brotliEncoding = StringValue(CoseValues.brotliContentEncoding);

final class _DocumentManagerImpl implements DocumentManager {
const _DocumentManagerImpl();

@override
Future<SignedDocument<T>> parseDocument<T extends BinaryDocument>(
Uint8List bytes, {
required DocumentParser<T> parser,
}) async {
final coseSign = CoseSign.fromCbor(cbor.decode(bytes));
final payload = await _brotliDecompressPayload(coseSign);
final document = parser(payload);
return _CoseSignedDocument(coseSign, document);
}

@override
Future<SignedDocument<T>> signDocument<T extends BinaryDocument>(
T document, {
required Uint8List publicKey,
required Uint8List privateKey,
}) async {
final compressedPayload = await _brotliCompressPayload(document.toBytes());

final coseSign = await CoseSign.sign(
protectedHeaders: CoseHeaders.protected(
contentEncoding: _brotliEncoding,
contentType: document.contentType.asCose,
),
unprotectedHeaders: const CoseHeaders.unprotected(),
payload: compressedPayload,
signers: [_Bip32Ed25519XSigner(publicKey, privateKey)],
);

return _CoseSignedDocument(coseSign, document);
}

Future<Uint8List> _brotliCompressPayload(Uint8List payload) async {
final compressor = CatalystCompression.instance.brotli;
final compressed = await compressor.compress(payload);
return Uint8List.fromList(compressed);
}

Future<Uint8List> _brotliDecompressPayload(CoseSign coseSign) async {
if (coseSign.protectedHeaders.contentEncoding == _brotliEncoding) {
final compressor = CatalystCompression.instance.brotli;
final decompressed = await compressor.decompress(coseSign.payload);
return Uint8List.fromList(decompressed);
} else {
return coseSign.payload;
}
}
}

final class _CoseSignedDocument<T extends BinaryDocument>
extends SignedDocument<T> with EquatableMixin {
final CoseSign _coseSign;

@override
final T document;

const _CoseSignedDocument(this._coseSign, this.document);

@override
Future<bool> verifySignature(Uint8List publicKey) async {
return _coseSign.verify(
verifier: _Bip32Ed25519XVerifier(publicKey),
);
}

@override
Uint8List toBytes() {
final bytes = cbor.encode(_coseSign.toCbor());
return Uint8List.fromList(bytes);
}

@override
List<Object?> get props => [_coseSign, document];
}

extension _CoseDocumentContentType on DocumentContentType {
/// Maps the [DocumentContentType] into COSE representation.
StringOrInt get asCose {
switch (this) {
case DocumentContentType.json:
return const IntValue(CoseValues.jsonContentType);
}
}
}

final class _Bip32Ed25519XSigner implements CatalystCoseSigner {
final Uint8List publicKey;
final Uint8List privateKey;

const _Bip32Ed25519XSigner(this.publicKey, this.privateKey);

@override
StringOrInt? get alg => const IntValue(CoseValues.eddsaAlg);

@override
Future<Uint8List?> get kid async => publicKey;

@override
Future<Uint8List> sign(Uint8List data) async {
final pk = Bip32Ed25519XPrivateKeyFactory.instance.fromBytes(privateKey);
final signature = await pk.sign(data);
return Uint8List.fromList(signature.bytes);
}
}

final class _Bip32Ed25519XVerifier implements CatalystCoseVerifier {
final Uint8List publicKey;

const _Bip32Ed25519XVerifier(this.publicKey);

@override
Future<Uint8List?> get kid async => publicKey;

@override
Future<bool> verify(Uint8List data, Uint8List signature) async {
final pk = Bip32Ed25519XPublicKeyFactory.instance.fromBytes(publicKey);
return pk.verify(
data,
signature: Bip32Ed25519XSignatureFactory.instance.fromBytes(signature),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ environment:

dependencies:
catalyst_cardano_serialization: ^0.4.0
catalyst_compression: ^0.3.0
catalyst_cose: ^0.3.0
catalyst_key_derivation: ^0.1.0
catalyst_voices_models:
path: ../catalyst_voices_models
cbor: ^6.2.0
collection: ^1.18.0
convert: ^3.1.1
cryptography: ^2.7.0
Expand Down
Loading
Loading