diff --git a/lib/src/models/data_item.dart b/lib/src/models/data_item.dart index 10e2acb..08a1578 100644 --- a/lib/src/models/data_item.dart +++ b/lib/src/models/data_item.dart @@ -2,16 +2,23 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:arweave/src/utils/bundle_tag_parser.dart'; +import 'package:json_annotation/json_annotation.dart'; import '../crypto/crypto.dart'; import '../utils.dart'; import 'models.dart'; +part 'data_item.g.dart'; + final MIN_BINARY_SIZE = 1044; /// ANS-104 [DataItem] /// Spec: https://github.com/joshbenaron/arweave-standards/blob/ans104/ans/ANS-104.md +@JsonSerializable(explicitToJson: true) class DataItem implements TransactionBase { + @JsonKey(defaultValue: 2) + final int format = 2; + @override String get id => _id; late String _id; @@ -34,28 +41,48 @@ class DataItem implements TransactionBase { @override late Uint8List data; + @JsonKey(name: 'data_size') + String get dataSize => _dataSize; + String _dataSize = '0'; + @override String get signature => _signature; late String _signature; + + @JsonKey(ignore: true) late ByteBuffer binary; /// This constructor is reserved for JSON serialisation. /// /// [DataItem.withJsonData()] and [DataItem.withBlobData()] are the recommended ways to construct data items. DataItem({ + String? id, + String? signature, String? owner, String? target, String? nonce, List? tags, String? data, Uint8List? dataBytes, + String? dataSize, }) : target = target ?? '', nonce = nonce ?? '', _owner = owner ?? '', data = data != null ? decodeBase64ToBytes(data) : (dataBytes ?? Uint8List(0)), - _tags = tags ?? []; + _tags = tags ?? [] { + if (dataSize != null) { + _dataSize = dataSize; + } + if (signature != null) { + _signature = signature; + } + + if (id != null) { + _id = id; + } + } /// Constructs a [DataItem] with the specified JSON data and appropriate Content-Type tag. factory DataItem.withJsonData({ @@ -87,11 +114,21 @@ class DataItem implements TransactionBase { nonce: nonce, tags: tags, dataBytes: data, + dataSize: data.lengthInBytes.toString(), ); @override void setOwner(String owner) => _owner = owner; + @override + void setId(String id) => _id = id; + + @override + void setSignature(String signature) => _signature = signature; + + @override + void setTags(List tags) => _tags = tags; + @override void addTag(String name, String value) { tags.add( @@ -106,7 +143,7 @@ class DataItem implements TransactionBase { Future getSignatureData() => deepHash( [ utf8.encode('dataitem'), - utf8.encode('1'), //Transaction format + utf8.encode(format.toString()), //Transaction format utf8.encode('1'), //Signature type decodeBase64ToBytes(owner), decodeBase64ToBytes(target), @@ -118,15 +155,12 @@ class DataItem implements TransactionBase { /// Signs the [DataItem] using the specified wallet and sets the `id` and `signature` appropriately. @override - Future sign(Wallet wallet) async { - final signatureData = await getSignatureData(); - final rawSignature = await wallet.sign(signatureData); - - _signature = encodeBytesToBase64(rawSignature); + Future sign(Wallet wallet) async { + final signedTransaction = await wallet.sign(this); - final idHash = await sha256.hash(rawSignature); - _id = encodeBytesToBase64(idHash.bytes); - return Uint8List.fromList(idHash.bytes); + setId(signedTransaction.id); + setSignature(signedTransaction.signature!); + setTags(signedTransaction.tags); } int getSize() { @@ -287,4 +321,21 @@ class DataItem implements TransactionBase { bytesBuilder.add(data); return bytesBuilder; } + + /// Encodes the [DataItem] as JSON with the `data` as the original unencoded [Uint8List]. + @override + Map toJson() => _$DataItemToJson(this); + + factory DataItem.fromJson(Map json) => + _$DataItemFromJson(json); + + @override + Map toUnsignedJson() => { + 'format': format, + 'owner': owner, + 'tags': tags.map((e) => e.toJson()).toList(), + 'target': target, + 'data': data, + 'data_size': dataSize, + }; } diff --git a/lib/src/models/data_item.g.dart b/lib/src/models/data_item.g.dart new file mode 100644 index 0000000..c06828e --- /dev/null +++ b/lib/src/models/data_item.g.dart @@ -0,0 +1,27 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'data_item.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +DataItem _$DataItemFromJson(Map json) => DataItem( + owner: json['owner'] as String?, + id: json['id'] as String?, + target: json['target'] as String?, + nonce: json['nonce'] as String?, + tags: (json['tags'] as List?) + ?.map((e) => Tag.fromJson(e as Map)) + .toList(), + data: json['data'] as String?, + signature: json['signature'] as String?, + ); + +Map _$DataItemToJson(DataItem instance) => { + 'owner': instance.owner, + 'target': instance.target, + 'nonce': instance.nonce, + 'tags': instance.tags.map((e) => e.toJson()).toList(), + 'data': instance.data, + }; diff --git a/lib/src/models/id.g.dart b/lib/src/models/id.g.dart index 9b8e9dc..2433538 100644 --- a/lib/src/models/id.g.dart +++ b/lib/src/models/id.g.dart @@ -6,14 +6,12 @@ part of 'id.dart'; // JsonSerializableGenerator // ************************************************************************** -ArweaveId _$ArweaveIdFromJson(Map json) { - return ArweaveId( - name: json['name'] as String, - url: json['url'] as String, - text: json['text'] as String, - avatarDataUri: json['avatarDataUri'] as String, - ); -} +ArweaveId _$ArweaveIdFromJson(Map json) => ArweaveId( + name: json['name'] as String, + url: json['url'] as String, + text: json['text'] as String, + avatarDataUri: json['avatarDataUri'] as String, + ); Map _$ArweaveIdToJson(ArweaveId instance) => { 'name': instance.name, diff --git a/lib/src/models/tag.dart b/lib/src/models/tag.dart index 26f0828..f28de3f 100644 --- a/lib/src/models/tag.dart +++ b/lib/src/models/tag.dart @@ -2,7 +2,7 @@ import 'package:json_annotation/json_annotation.dart'; part 'tag.g.dart'; -@JsonSerializable() +@JsonSerializable(explicitToJson: true) class Tag { /// The tag's name encoded as Base64. final String name; diff --git a/lib/src/models/tag.g.dart b/lib/src/models/tag.g.dart index 80dfe7d..b445f30 100644 --- a/lib/src/models/tag.g.dart +++ b/lib/src/models/tag.g.dart @@ -6,12 +6,10 @@ part of 'tag.dart'; // JsonSerializableGenerator // ************************************************************************** -Tag _$TagFromJson(Map json) { - return Tag( - json['name'] as String, - json['value'] as String, - ); -} +Tag _$TagFromJson(Map json) => Tag( + json['name'] as String, + json['value'] as String, + ); Map _$TagToJson(Tag instance) => { 'name': instance.name, diff --git a/lib/src/models/transaction-base.dart b/lib/src/models/transaction-base.dart index e3646d2..ef05544 100644 --- a/lib/src/models/transaction-base.dart +++ b/lib/src/models/transaction-base.dart @@ -19,6 +19,12 @@ abstract class TransactionBase { void setOwner(String owner); + void setId(String id); + + void setSignature(String signature); + + void setTags(List tags); + void addTag(String name, String value); /// Returns the message that should be signed to produce a valid signature. @@ -27,4 +33,7 @@ abstract class TransactionBase { Future sign(Wallet wallet); Future verify(); + + Map toJson(); + Map toUnsignedJson(); } diff --git a/lib/src/models/transaction.dart b/lib/src/models/transaction.dart index 7317c38..d8c0655 100644 --- a/lib/src/models/transaction.dart +++ b/lib/src/models/transaction.dart @@ -12,9 +12,9 @@ part 'transaction.g.dart'; String _bigIntToString(BigInt v) => v.toString(); BigInt _stringToBigInt(String v) => BigInt.parse(v); -@JsonSerializable() +@JsonSerializable(explicitToJson: true) class Transaction implements TransactionBase { - @JsonKey(defaultValue: 1) + @JsonKey(defaultValue: 2) final int format; @override @@ -174,6 +174,15 @@ class Transaction implements TransactionBase { @override void setOwner(String owner) => _owner = owner; + @override + void setId(String id) => _id = id; + + @override + void setSignature(String signature) => _signature = signature; + + @override + void setTags(List tags) => _tags = tags; + /// Sets the data and data size of this [Transaction]. /// /// Also chunks and validates the incoming data for format 2 transactions. @@ -277,13 +286,11 @@ class Transaction implements TransactionBase { @override Future sign(Wallet wallet) async { - final signatureData = await getSignatureData(); - final rawSignature = await wallet.sign(signatureData); - - _signature = encodeBytesToBase64(rawSignature); + final signedTransaction = await wallet.sign(this); - final idHash = await sha256.hash(rawSignature); - _id = encodeBytesToBase64(idHash.bytes); + setId(signedTransaction.id); + setSignature(signedTransaction.signature!); + setTags(signedTransaction.tags); } @override @@ -312,5 +319,20 @@ class Transaction implements TransactionBase { _$TransactionFromJson(json); /// Encodes the [Transaction] as JSON with the `data` as the original unencoded [Uint8List]. + @override Map toJson() => _$TransactionToJson(this); + + @override + Map toUnsignedJson() => { + 'format': format, + 'last_tx': lastTx, + 'owner': owner, + 'tags': tags.map((e) => e.toJson()).toList(), + 'target': target, + 'quantity': _bigIntToString(quantity), + 'data': data, + 'data_size': dataSize, + 'data_root': dataRoot, + 'reward': _bigIntToString(reward), + }; } diff --git a/lib/src/models/transaction.g.dart b/lib/src/models/transaction.g.dart index 9d2d656..71f5fcc 100644 --- a/lib/src/models/transaction.g.dart +++ b/lib/src/models/transaction.g.dart @@ -6,24 +6,22 @@ part of 'transaction.dart'; // JsonSerializableGenerator // ************************************************************************** -Transaction _$TransactionFromJson(Map json) { - return Transaction( - format: json['format'] as int? ?? 1, - id: json['id'] as String?, - lastTx: json['last_tx'] as String?, - owner: json['owner'] as String?, - tags: (json['tags'] as List?) - ?.map((e) => Tag.fromJson(e as Map)) - .toList(), - target: json['target'] as String?, - quantity: _stringToBigInt(json['quantity'] as String), - data: json['data'] as String?, - dataSize: json['data_size'] as String?, - dataRoot: json['data_root'] as String?, - reward: _stringToBigInt(json['reward'] as String), - signature: json['signature'] as String?, - ); -} +Transaction _$TransactionFromJson(Map json) => Transaction( + format: json['format'] as int? ?? 1, + id: json['id'] as String?, + lastTx: json['last_tx'] as String?, + owner: json['owner'] as String?, + tags: (json['tags'] as List?) + ?.map((e) => Tag.fromJson(e as Map)) + .toList(), + target: json['target'] as String?, + quantity: _stringToBigInt(json['quantity'] as String), + data: json['data'] as String?, + dataSize: json['data_size'] as String?, + dataRoot: json['data_root'] as String?, + reward: _stringToBigInt(json['reward'] as String), + signature: json['signature'] as String?, + ); Map _$TransactionToJson(Transaction instance) => { @@ -31,7 +29,7 @@ Map _$TransactionToJson(Transaction instance) => 'id': instance.id, 'last_tx': instance.lastTx, 'owner': instance.owner, - 'tags': instance.tags, + 'tags': instance.tags.map((e) => e.toJson()).toList(), 'target': instance.target, 'quantity': _bigIntToString(instance.quantity), 'data': instance.data, diff --git a/lib/src/models/wallet.dart b/lib/src/models/wallet.dart index 8c5ab87..8ac30d2 100644 --- a/lib/src/models/wallet.dart +++ b/lib/src/models/wallet.dart @@ -3,6 +3,7 @@ import 'dart:core'; import 'dart:math'; import 'dart:typed_data'; +import 'package:arweave/arweave.dart'; import 'package:cryptography/cryptography.dart'; import 'package:jwk/jwk.dart'; import 'package:pointycastle/export.dart'; @@ -54,7 +55,19 @@ class Wallet { await _keyPair!.extractPublicKey().then((res) => res.n)); Future getAddress() async => ownerToAddress(await getOwner()); - Future sign(Uint8List message) async => + Future sign(TransactionBase transaction) async { + final rawSignature = await rsaPssSign( + message: await transaction.getSignatureData(), keyPair: _keyPair!); + final signature = encodeBytesToBase64(rawSignature); + + final idHash = await sha256.hash(rawSignature); + final id = encodeBytesToBase64(idHash.bytes); + return transaction + ..setId(id) + ..setSignature(signature); + } + + Future signMessage(Uint8List message) async => rsaPssSign(message: message, keyPair: _keyPair!); factory Wallet.fromJwk(Map jwk) { diff --git a/pubspec.yaml b/pubspec.yaml index 0f2dc08..e0ea364 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ environment: dependencies: cryptography: ^2.0.1 http: ^0.13.3 - json_annotation: ^4.0.1 + json_annotation: 4.4.0 jwk: ^0.1.0 meta: ^1.7.0 pointycastle: ^3.1.1 diff --git a/test/wallets_test.dart b/test/wallets_test.dart index 9a40c65..686837a 100644 --- a/test/wallets_test.dart +++ b/test/wallets_test.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:arweave/arweave.dart'; -import 'package:arweave/utils.dart' as utils; +import 'package:arweave/utils.dart'; import 'package:test/test.dart'; import 'utils.dart'; @@ -62,9 +62,9 @@ void main() { final wallet = await getTestWallet(); final message = utf8.encode(''); - final signature = await wallet.sign(message as Uint8List); + final signature = await wallet.signMessage(message as Uint8List); expect( - utils.encodeBytesToBase64(signature), + encodeBytesToBase64(signature), startsWith('II5LxGnPt4WTSz9P__wMAdjzXWlZE-wGbKU7wm4DbGuPXB5Vifs'), ); }, onPlatform: {