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-5747: feat: added Signers to work with signing of non-arweave wallets #62

Merged
merged 7 commits into from
Apr 15, 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
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"editor.codeActionsOnSave": {
"source.organizeImports": true
"source.organizeImports": "explicit"
}
}
4 changes: 2 additions & 2 deletions example/data_bundles.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ void main() async {
..addTag('MyTag', '0')
..addTag('OtherTag', 'Foo');

await dataItem.sign(wallet);
await dataItem.sign(ArweaveSigner(wallet));

// Prepare a data bundle transaction.
final transaction = await client.transactions.prepare(
Expand All @@ -31,7 +31,7 @@ void main() async {
);

// Sign the bundle transaction.
await transaction.sign(wallet);
await transaction.sign(ArweaveSigner(wallet));

// Upload the transaction.
await client.transactions.post(transaction);
Expand Down
2 changes: 1 addition & 1 deletion example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ void main() async {
..addTag('App-Version', '1.0.0');

// Sign the transaction.
await transaction.sign(wallet);
await transaction.sign(ArweaveSigner(wallet));

// Upload the transaction in a single call:
await client.transactions.post(transaction);
Expand Down
2 changes: 2 additions & 0 deletions lib/arweave.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export './src/arweave.dart';
export './src/models/models.dart';
export './src/signature_config.dart';
export './src/signer.dart';
export './src/streams/streams.dart';
113 changes: 66 additions & 47 deletions lib/src/models/data_item.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:arweave/arweave.dart';
import 'package:arweave/src/utils/bundle_tag_parser.dart';

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

final MIN_BINARY_SIZE = 1044;

/// ANS-104 [DataItem]
/// Spec: https://github.com/joshbenaron/arweave-standards/blob/ans104/ans/ANS-104.md
Expand Down Expand Up @@ -39,6 +37,10 @@ class DataItem implements TransactionBase {
late String _signature;
late ByteBuffer binary;

@override
SignatureConfig? get signatureConfig => _signatureConfig;
late SignatureConfig _signatureConfig;

/// This constructor is reserved for JSON serialisation.
///
/// [DataItem.withJsonData()] and [DataItem.withBlobData()] are the recommended ways to construct data items.
Expand Down Expand Up @@ -103,24 +105,30 @@ class DataItem implements TransactionBase {
}

@override
Future<Uint8List> getSignatureData() => deepHash(
[
utf8.encode('dataitem'),
utf8.encode('1'), //Transaction format
utf8.encode('1'), //Signature type
decodeBase64ToBytes(owner),
decodeBase64ToBytes(target),
decodeBase64ToBytes(nonce),
serializeTags(tags: tags),
data,
],
);
Future<Uint8List> getSignatureData() {
print(
'DataItem signatureType ${signatureConfig!.signatureType.toString()}');
return deepHash(
[
utf8.encode('dataitem'),
utf8.encode('1'), //Transaction format
utf8.encode(signatureConfig!.signatureType.toString()), //Signature type
decodeBase64ToBytes(owner),
decodeBase64ToBytes(target),
decodeBase64ToBytes(nonce),
serializeTags(tags: tags),
data,
],
);
}

/// Signs the [DataItem] using the specified wallet and sets the `id` and `signature` appropriately.
@override
Future<Uint8List> sign(Wallet wallet) async {
Future<Uint8List> sign(Signer signer) async {
_signatureConfig = signer.signatureConfig;

final signatureData = await getSignatureData();
final rawSignature = await wallet.sign(signatureData);
final rawSignature = await signer.sign(signatureData);

_signature = encodeBytesToBase64(rawSignature);

Expand All @@ -136,8 +144,8 @@ class DataItem implements TransactionBase {
final serializedTags = serializeTags(tags: tags);
final tagsLength = 16 + serializedTags.lengthInBytes;

const arweaveSignerLength = 512;
const ownerLength = 512;
final arweaveSignerLength = signatureConfig!.signatureLength;
final ownerLength = signatureConfig!.publicKeyLength;

const signatureTypeLength = 2;

Expand All @@ -158,23 +166,34 @@ class DataItem implements TransactionBase {
Future<bool> verify() async {
final buffer = (await asBinary()).toBytes().buffer;
try {
if (buffer.lengthInBytes < MIN_BINARY_SIZE) {
// NOTE: This was previously hardcoded to 1044, using calculation here
// factoring in singnatureConfig, but this value should be checked
final minBinarySize = 20 +
signatureConfig!.signatureLength +
signatureConfig!.publicKeyLength;

if (buffer.lengthInBytes < minBinarySize) {
return false;
}
final sigType = byteArrayToLong(buffer.asUint8List().sublist(0, 2));
assert(sigType == 1);
var tagsStart = 2 + 512 + 512 + 2;
final targetPresent = buffer.asUint8List()[1026] == 1;
tagsStart += targetPresent ? 32 : 0;
final anchorPresentByte = targetPresent ? 1059 : 1027;
final anchorPresent = buffer.asUint8List()[anchorPresentByte] == 1;
tagsStart += anchorPresent ? 32 : 0;
assert(sigType == signatureConfig!.signatureType);

final targetStart = 2 +
signatureConfig!.signatureLength +
signatureConfig!.publicKeyLength;
final targetPresent = buffer.asUint8List()[targetStart] == 1;
final anchorStart = targetStart + (targetPresent ? 33 : 1);
final anchorPresent = buffer.asUint8List()[anchorStart] == 1;

final tagsStart = anchorStart + (anchorPresent ? 33 : 1);

final numberOfTags = byteArrayToLong(
buffer.asUint8List().sublist(tagsStart, tagsStart + 8));
final numberOfTagBytesArray =
buffer.asUint8List().sublist(tagsStart + 8, tagsStart + 16);
final numberOfTagBytes = byteArrayToLong(numberOfTagBytesArray);

// FIXME: This is not being verified
// final numberOfTagBytesArray =
// buffer.asUint8List().sublist(tagsStart + 8, tagsStart + 16);
// final numberOfTagBytes = byteArrayToLong(numberOfTagBytesArray);

if (numberOfTags > 0) {
try {
Expand All @@ -195,12 +214,8 @@ class DataItem implements TransactionBase {

if (id != expectedId) return false;

return rsaPssVerify(
input: signatureData,
signature: claimedSignatureBytes,
modulus: decodeBase64ToBigInt(owner),
publicExponent: publicExponent,
);
return signatureConfig!
.verify(signatureData, claimedSignatureBytes, owner);
} catch (_) {
return false;
}
Expand All @@ -217,12 +232,13 @@ class DataItem implements TransactionBase {
}

int getTagsStart() {
var tagsStart = 2 + 512 + 512 + 2;
var targetPresent = binary.asUint8List()[1026] == 1;
tagsStart += targetPresent ? 32 : 0;
var anchorPresentByte = targetPresent ? 1059 : 1027;
var anchorPresent = binary.asUint8List()[anchorPresentByte] == 1;
tagsStart += anchorPresent ? 32 : 0;
final targetStart =
2 + signatureConfig!.signatureLength + signatureConfig!.publicKeyLength;
final targetPresent = binary.asUint8List()[targetStart] == 1;
final anchorStart = targetStart + (targetPresent ? 33 : 1);
final anchorPresent = binary.asUint8List()[anchorStart] == 1;

final tagsStart = anchorStart + (anchorPresent ? 33 : 1);

return tagsStart;
}
Expand All @@ -240,14 +256,16 @@ class DataItem implements TransactionBase {

// Returns the start byte of the tags section (number of tags)
int getTargetStart() {
return 1026;
return 2 +
signatureConfig!.signatureLength +
signatureConfig!.publicKeyLength;
}

// Returns the start byte of the tags section (number of tags)
int getAnchorStart() {
var anchorStart = getTargetStart() + 1;
var anchorStart = getTargetStart();
final targetPresent = binary.asUint8List()[getTargetStart()] == 1;
anchorStart += targetPresent ? 32 : 0;
anchorStart += targetPresent ? 33 : 1;

return anchorStart;
}
Expand All @@ -259,10 +277,11 @@ class DataItem implements TransactionBase {
final tags = serializeTags(tags: this.tags);

// See [https://github.com/joshbenaron/arweave-standards/blob/ans104/ans/ANS-104.md#13-dataitem-format]
assert(decodedOwner.buffer.lengthInBytes == 512);
assert(
decodedOwner.buffer.lengthInBytes == signatureConfig!.publicKeyLength);
final bytesBuilder = BytesBuilder();

bytesBuilder.add(shortTo2ByteArray(1));
bytesBuilder.add(shortTo2ByteArray(signatureConfig!.signatureType));
bytesBuilder.add(decodeBase64ToBytes(signature));
bytesBuilder.add(decodedOwner);
bytesBuilder.addByte(decodedTarget.isNotEmpty ? 1 : 0);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/models/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ export 'data_item.dart';
export 'data_item_handle.dart';
export 'id.dart';
export 'tag.dart';
export 'transaction-base.dart';
export 'transaction.dart';
export 'transaction_base.dart';
export 'transaction_chunk.dart';
export 'transaction_stream.dart';
export 'transaction_uploader.dart';
Expand Down
18 changes: 10 additions & 8 deletions lib/src/models/transaction.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class Transaction implements TransactionBase {
String get signature => _signature;
late String _signature;

@override
SignatureConfig? get signatureConfig => _signatureConfig;
late SignatureConfig _signatureConfig;

@JsonKey(ignore: true)
TransactionChunksWithProofs? get chunks => _chunks;
TransactionChunksWithProofs? _chunks;
Expand Down Expand Up @@ -287,11 +291,13 @@ class Transaction implements TransactionBase {
}

@override
Future<void> sign(Wallet wallet) async {
Future<void> sign(Signer signer) async {
final signatureData = await getSignatureData();
final rawSignature = await wallet.sign(signatureData);

final rawSignature = await signer.sign(signatureData);

_signature = encodeBytesToBase64(rawSignature);
_signatureConfig = signer.signatureConfig;

final idHash = await sha256.hash(rawSignature);
_id = encodeBytesToBase64(idHash.bytes);
Expand All @@ -308,12 +314,8 @@ class Transaction implements TransactionBase {

if (id != expectedId) return false;

return rsaPssVerify(
input: signatureData,
signature: claimedSignatureBytes,
modulus: decodeBase64ToBigInt(owner!),
publicExponent: publicExponent,
);
return signatureConfig!
.verify(signatureData, claimedSignatureBytes, owner!);
} catch (_) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'dart:typed_data';

import 'models.dart';
import 'package:arweave/arweave.dart';

abstract class TransactionBase {
String get id;
Expand All @@ -17,14 +17,17 @@ abstract class TransactionBase {
//Null signature means the transaction hasnt been signed
String? get signature;

//Null signature config means the transaction hasnt been signed
SignatureConfig? get signatureConfig;

void setOwner(String owner);

void addTag(String name, String value);

/// Returns the message that should be signed to produce a valid signature.
Future<Uint8List> getSignatureData();

Future<void> sign(Wallet wallet);
Future<void> sign(Signer signer);

Future<bool> verify();
}
18 changes: 9 additions & 9 deletions lib/src/models/transaction_stream.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:async/async.dart';
import 'package:json_annotation/json_annotation.dart';

import '../crypto/crypto.dart';
import '../streams/data_models.dart';
import '../utils.dart';

part 'transaction_stream.g.dart';
Expand Down Expand Up @@ -74,6 +73,10 @@ class TransactionStream implements Transaction {
String get signature => _signature;
late String _signature;

@override
SignatureConfig? get signatureConfig => _signatureConfig;
late SignatureConfig _signatureConfig;

@JsonKey(ignore: true)
TransactionChunksWithProofs? get chunks => _chunks;
TransactionChunksWithProofs? _chunks;
Expand Down Expand Up @@ -298,11 +301,12 @@ class TransactionStream implements Transaction {
}

@override
Future<void> sign(Wallet wallet) async {
Future<void> sign(Signer signer) async {
final signatureData = await getSignatureData();
final rawSignature = await wallet.sign(signatureData);
final rawSignature = await signer.sign(signatureData);

_signature = encodeBytesToBase64(rawSignature);
_signatureConfig = signer.signatureConfig;

final idHash = await sha256.hash(rawSignature);
_id = encodeBytesToBase64(idHash.bytes);
Expand All @@ -319,12 +323,8 @@ class TransactionStream implements Transaction {

if (id != expectedId) return false;

return rsaPssVerify(
input: signatureData,
signature: claimedSignatureBytes,
modulus: decodeBase64ToBigInt(owner!),
publicExponent: publicExponent,
);
return signatureConfig!
.verify(signatureData, claimedSignatureBytes, owner!);
} catch (_) {
return false;
}
Expand Down
3 changes: 3 additions & 0 deletions 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:typed_data';

import 'package:arweave/src/crypto/hmac_drbg_secure_random.dart';
import 'package:arweave/src/signature_config.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:cryptography/cryptography.dart' hide SecureRandom;
import 'package:jwk/jwk.dart';
Expand Down Expand Up @@ -76,6 +77,8 @@ class Wallet {
Future<Uint8List> sign(Uint8List message) async =>
rsaPssSign(message: message, keyPair: _keyPair!);

SignatureConfig getSignatureConfig() => SignatureConfig.arweave;

factory Wallet.fromJwk(Map<String, dynamic> jwk) {
// Normalize the JWK so that it can be decoded by 'cryptography'.
jwk = jwk.map((key, value) {
Expand Down
Loading
Loading