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

PE-1136 Refactor ArConnect Signing #20

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
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
71 changes: 61 additions & 10 deletions lib/src/models/data_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Tag>? 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;
karlprieb marked this conversation as resolved.
Show resolved Hide resolved
}
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({
Expand Down Expand Up @@ -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<Tag> tags) => _tags = tags;

@override
void addTag(String name, String value) {
tags.add(
Expand All @@ -106,7 +143,7 @@ class DataItem implements TransactionBase {
Future<Uint8List> 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),
Expand All @@ -118,15 +155,12 @@ class DataItem implements TransactionBase {

/// Signs the [DataItem] using the specified wallet and sets the `id` and `signature` appropriately.
@override
Future<Uint8List> sign(Wallet wallet) async {
final signatureData = await getSignatureData();
final rawSignature = await wallet.sign(signatureData);

_signature = encodeBytesToBase64(rawSignature);
Future<void> 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() {
Expand Down Expand Up @@ -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<String, dynamic> toJson() => _$DataItemToJson(this);

factory DataItem.fromJson(Map<String, dynamic> json) =>
_$DataItemFromJson(json);

@override
Map<String, dynamic> toUnsignedJson() => <String, dynamic>{
'format': format,
'owner': owner,
'tags': tags.map((e) => e.toJson()).toList(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid one-character identifiers such as e in this case

'target': target,
'data': data,
'data_size': dataSize,
};
}
27 changes: 27 additions & 0 deletions lib/src/models/data_item.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions lib/src/models/id.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/src/models/tag.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 4 additions & 6 deletions lib/src/models/tag.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions lib/src/models/transaction-base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ abstract class TransactionBase {

void setOwner(String owner);

void setId(String id);

void setSignature(String signature);

void setTags(List<Tag> tags);

void addTag(String name, String value);

/// Returns the message that should be signed to produce a valid signature.
Expand All @@ -27,4 +33,7 @@ abstract class TransactionBase {
Future<void> sign(Wallet wallet);

Future<bool> verify();

Map<String, dynamic> toJson();
Map<String, dynamic> toUnsignedJson();
}
38 changes: 30 additions & 8 deletions lib/src/models/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Tag> tags) => _tags = tags;

/// Sets the data and data size of this [Transaction].
///
/// Also chunks and validates the incoming data for format 2 transactions.
Expand Down Expand Up @@ -277,13 +286,11 @@ class Transaction implements TransactionBase {

@override
Future<void> 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
Expand Down Expand Up @@ -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<String, dynamic> toJson() => _$TransactionToJson(this);

@override
Map<String, dynamic> toUnsignedJson() => <String, dynamic>{
'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),
};
}
36 changes: 17 additions & 19 deletions lib/src/models/transaction.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 14 additions & 1 deletion lib/src/models/wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -54,7 +55,19 @@ class Wallet {
await _keyPair!.extractPublicKey().then((res) => res.n));
Future<String> getAddress() async => ownerToAddress(await getOwner());

Future<Uint8List> sign(Uint8List message) async =>
Future<TransactionBase> 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<Uint8List> signMessage(Uint8List message) async =>
rsaPssSign(message: message, keyPair: _keyPair!);

factory Wallet.fromJwk(Map<String, dynamic> jwk) {
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading