Skip to content

Commit

Permalink
feat: added Signers to work with signing of non-arweave wallets (e.g.…
Browse files Browse the repository at this point in the history
…, ETH)
  • Loading branch information
kunstmusik committed Mar 15, 2024
1 parent 41d5906 commit ac2c39a
Show file tree
Hide file tree
Showing 17 changed files with 225 additions and 97 deletions.
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

0 comments on commit ac2c39a

Please sign in to comment.