Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
karlprieb committed Aug 29, 2023
1 parent 91f5ce8 commit 25ee6ef
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 70 deletions.
Binary file added lib/src/.DS_Store
Binary file not shown.
299 changes: 278 additions & 21 deletions lib/src/streams/data_item.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,137 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:arweave/utils.dart';
import 'package:async/async.dart';
import 'package:fpdart/fpdart.dart';

import '../crypto/crypto.dart';
import '../models/models.dart';
import '../utils/bundle_tag_parser.dart';
import 'utils.dart';

TaskEither<DataItemError, DataItemResult> createDataItemTaskEither({
required final Wallet wallet,
required final DataStreamGenerator dataStream,
required final int dataSize,
final String target = '',
final String anchor = '',
final List<Tag> tags = const [],
}) {
return TaskEither<DataItemError, DataItemResult>.Do((_) async {
final owner = await _(getOwnerTaskEither(wallet));
final ownerBytes = await _(decodeBase64ToBytesTaskEither(owner));
final targetBytes = await _(decodeAndCheckTargetTaskEither(target));
final anchorBytes = await _(decodeAndCheckAnchorTaskEither(anchor));
final tagsBytes = await _(serializeTagsTaskEither(tags));
final signatureData = await _(
deepHashTaskEither([
toStream(utf8.encode('dataitem')),
toStream(utf8.encode('1')), // Transaction format
toStream(utf8.encode('1')),
toStream(ownerBytes),
toStream(targetBytes),
toStream(anchorBytes),
toStream(tagsBytes),
dataStream(),
]),
);
final signResult = await _(
signDataItemTaskEither(
wallet: wallet,
signatureData: signatureData,
),
);

final dataItemHeaders = [
...shortTo2ByteArray(1),
...signResult.signature,
...ownerBytes,
...targetBytes.isEmpty ? [0] : [1],
...targetBytes,
...anchorBytes.isEmpty ? [0] : [1],
...anchorBytes,
...longTo8ByteArray(tags.length),
...longTo8ByteArray(tagsBytes.lengthInBytes),
...tagsBytes,
];

dataStreamGenerator() => concatenateStreams([
Stream.fromIterable([dataItemHeaders]),
dataStream()
]);

return DataItemResult(
id: signResult.id,
size: dataItemHeaders.length + dataSize,
stream: dataStreamGenerator,
);
}).getOrElse((_) => TaskEither.left(DataItemCreationError()));
}

// TaskEither<DataItemError, DataItemResult> createDataItemTaskEither({
// required final Wallet wallet,
// required final DataStreamGenerator dataStream,
// required final int dataSize,
// final String target = '',
// final String anchor = '',
// final List<Tag> tags = const [],
// }) {
// return getOwnerTaskEither(wallet).flatMap((owner) =>
// decodeBase64ToBytesTaskEither(owner).flatMap((ownerBytes) =>
// decodeBase64ToBytesTaskEither(target).flatMap((targetBytes) {
// if (target.isNotEmpty && targetBytes.length != 32) {
// return TaskEither.left(InvalidTargetSizeError());
// }
//
// return decodeBase64ToBytesTaskEither(anchor).flatMap((anchorBytes) {
// if (anchor.isNotEmpty && anchorBytes.length != 32) {
// return TaskEither.left(InvalidAnchorSizeError());
// }
//
// final tagsBytes = serializeTags(tags: tags);
//
// return deepHashTaskEither([
// toStream(utf8.encode('dataitem')),
// toStream(utf8.encode('1')), // Transaction format
// toStream(utf8.encode('1')),
// toStream(ownerBytes),
// toStream(targetBytes),
// toStream(anchorBytes),
// toStream(tagsBytes),
// dataStream(),
// ]).flatMap((signatureData) => signDataItemTaskEither(
// wallet: wallet, signatureData: signatureData)
// .flatMap((signResult) {
// final dataItemHeaders = [
// ...shortTo2ByteArray(1),
// ...signResult.signature,
// ...ownerBytes,
// ...(targetBytes.isEmpty ? [0] : [1]),
// ...targetBytes,
// ...(anchorBytes.isEmpty ? [0] : [1]),
// ...anchorBytes,
// ...longTo8ByteArray(tags.length),
// ...longTo8ByteArray(tagsBytes.lengthInBytes),
// ...tagsBytes,
// ];
//
// dataStreamGenerator() => concatenateStreams([
// Stream.fromIterable([dataItemHeaders]),
// dataStream()
// ]);
//
// return TaskEither.of(DataItemResult(
// id: signResult.id,
// size: dataItemHeaders.length + dataSize,
// stream: dataStreamGenerator,
// ));
// }));
// });
// })));
// }

Future<(String, int, Future<Stream<List<int>>> Function())> createDataItem({
required Wallet wallet,
String target = '',
Expand Down Expand Up @@ -73,12 +196,12 @@ Future<(String, int, Future<Stream<List<int>>> Function())> createDataItem({
}

Future<ProcessedDataItem> processDataItem({
required Stream<List<int>> stream,
required Stream<List<int>> Function() dataItemStreamGenerator,
required String id,
required int length,
}) async {
int byteIndex = 0;
final reader = ChunkedStreamReader(stream);
final reader = ChunkedStreamReader(dataItemStreamGenerator());

// get signature type
final signatureType = decodeBytesToLong(await reader.readBytes(2));
Expand Down Expand Up @@ -129,6 +252,10 @@ Future<ProcessedDataItem> processDataItem({
// get data
final dataLength = length - byteIndex;
final dataStream = reader.readStream(dataLength);
final dataStart = byteIndex;
final dataEnd = length;
dataStreamGenerator() =>
byteRangeStream(dataItemStreamGenerator(), dataStart, dataEnd);

// verify
final signatureData = await deepHash([
Expand Down Expand Up @@ -159,13 +286,152 @@ Future<ProcessedDataItem> processDataItem({
}

return ProcessedDataItem(
id: id,
tags: tags,
signature: encodeBytesToBase64(signature),
owner: encodeBytesToBase64(owner),
target: encodeBytesToBase64(target),
anchor: encodeBytesToBase64(anchor),
dataLength: dataLength);
id: id,
tags: tags,
signature: encodeBytesToBase64(signature),
owner: encodeBytesToBase64(owner),
target: encodeBytesToBase64(target),
anchor: encodeBytesToBase64(anchor),
dataLength: dataLength,
dataStreamGenerator: dataStreamGenerator,
);
}

TaskEither<DataItemError, Uint8List> deepHashTaskEither(
final List<Stream<List<int>>> inputs,
) {
return TaskEither.tryCatch(() async {
return await deepHash(inputs);
}, (error, _) => DeepHashError());
}

TaskEither<DataItemError, SignDataItemResult> signDataItemTaskEither({
required final Wallet wallet,
required final Uint8List signatureData,
}) {
return TaskEither.tryCatch(() async {
final signature = await wallet.sign(signatureData);
final idHash = await sha256.hash(signature);

return SignDataItemResult(
id: encodeBytesToBase64(idHash.bytes),
signature: signature,
);
}, (error, _) => SignatureError());
}

TaskEither<DataItemError, String> getOwnerTaskEither(final Wallet wallet) =>
TaskEither.tryCatch(() async {
return await wallet.getOwner();
}, (error, _) => GetWalletOwnerError());

TaskEither<DataItemError, bool> verifySignatureTaskEither({
required final Uint8List owner,
required final Uint8List signature,
required final Uint8List signatureData,
required final String expectedId,
}) {
return TaskEither.tryCatch(() async {
final idHash = await sha256.hash(signature);
final id = encodeBytesToBase64(idHash.bytes);

if (expectedId != id) {
throw Exception("ID doesn't match signature");
}

final signVerification = await rsaPssVerify(
input: signatureData,
signature: signature,
modulus: decodeBytesToBigInt(owner),
publicExponent: BigInt.from(65537));

if (!signVerification) {
throw Exception("Invalid signature");
}

return signVerification;
}, (error, _) => SignatureError());
}

TaskEither<DataItemError, Uint8List> decodeBase64ToBytesTaskEither(
final String input,
) {
return TaskEither.tryCatch(() async {
return decodeBase64ToBytes(input);
}, (error, _) => DecodeBase64ToBytesError());
}

TaskEither<DataItemError, Uint8List> decodeAndCheckTargetTaskEither(
final String target) {
return TaskEither.tryCatch(() async {
final targetBytes = decodeBase64ToBytes(target);
if (targetBytes.length != 32) {
TaskEither.left(InvalidTargetSizeError());
}
return targetBytes;
}, (error, _) => DecodeBase64ToBytesError());
}

TaskEither<DataItemError, Uint8List> decodeAndCheckAnchorTaskEither(
final String target) {
return TaskEither.tryCatch(() async {
final anchorBytes = decodeBase64ToBytes(target);
if (anchorBytes.length != 32) {
TaskEither.left(InvalidAnchorSizeError());
}
return anchorBytes;
}, (error, _) => DecodeBase64ToBytesError());
}

TaskEither<DataItemError, Uint8List> serializeTagsTaskEither(
final List<Tag> tags) {
return TaskEither.tryCatch(() async {
return serializeTags(tags: tags);
}, (error, _) => SerializeTagsError());
}

abstract class DataItemError {}

class DataItemCreationError extends DataItemError {}

class InvalidTargetSizeError extends DataItemError {}

class InvalidAnchorSizeError extends DataItemError {}

class DeepHashError extends DataItemError {}

class SignatureError extends DataItemError {}

class GetWalletOwnerError extends DataItemError {}

class ProcessedDataItemHeadersError extends DataItemError {}

class DecodeBase64ToBytesError extends DataItemError {}

class SerializeTagsError extends DataItemError {}

class SignDataItemResult {
final String id;
final Uint8List signature;

const SignDataItemResult({
required this.id,
required this.signature,
});
}

typedef DataStreamGenerator = Stream<List<int>> Function();

class DataItemResult {
final String id;
final int size;
final DataStreamGenerator stream;

const DataItemResult({
required this.id,
required this.size,
required this.stream,
});
}

class ProcessedDataItem {
Expand All @@ -176,25 +442,16 @@ class ProcessedDataItem {
final String anchor;
final List<int> tags;
final int dataLength;
final DataStreamGenerator dataStreamGenerator;

ProcessedDataItem({
const ProcessedDataItem({
required this.id,
required this.signature,
required this.owner,
required this.target,
required this.anchor,
required this.tags,
required this.dataLength,
required this.dataStreamGenerator,
});

Map<String, dynamic> toMap() {
return {
'signature': signature,
'owner': owner,
'target': target,
'anchor': anchor,
'tags': tags,
'dataLength': dataLength,
};
}
}
8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
fpdart:
dependency: "direct main"
description:
name: fpdart
sha256: "7413acc5a6569a3fe8277928fc7487f3198530f0c4e635d0baef199ea36e8ee9"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
frontend_server_client:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies:
crypto: ^3.0.3
vm_service: ^11.9.0
intl: ^0.18.1
fpdart: ^1.1.0

dev_dependencies:
build_runner: ^2.0.4
Expand Down
Loading

0 comments on commit 25ee6ef

Please sign in to comment.