This repository has been archived by the owner on Feb 4, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 273
/
Copy pathcredentials.dart
129 lines (103 loc) · 4.7 KB
/
credentials.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:collection/collection.dart';
import '../../web3dart.dart' show Transaction;
import '../crypto/formatting.dart';
import '../crypto/keccak.dart';
import '../crypto/secp256k1.dart';
import '../crypto/secp256k1.dart' as secp256k1;
import '../utils/typed_data.dart';
import 'address.dart';
/// Anything that can sign payloads with a private key.
abstract class Credentials {
static const _messagePrefix = '\u0019Ethereum Signed Message:\n';
/// Whether these [Credentials] are safe to be copied to another isolate and
/// can operate there.
/// If this getter returns true, the client might chose to perform the
/// expensive signing operations on another isolate.
bool get isolateSafe => false;
/// Loads the ethereum address specified by these credentials.
Future<EthereumAddress> extractAddress();
/// Signs the [payload] with a private key. The output will be like the
/// bytes representation of the [eth_sign RPC method](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign),
/// but without the "Ethereum signed message" prefix.
/// The [payload] parameter contains the raw data, not a hash.
Future<Uint8List> sign(Uint8List payload, {int? chainId}) async {
final signature = await signToSignature(payload, chainId: chainId);
final r = padUint8ListTo32(unsignedIntToBytes(signature.r));
final s = padUint8ListTo32(unsignedIntToBytes(signature.s));
final v = unsignedIntToBytes(BigInt.from(signature.v));
// https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
return uint8ListFromList(r + s + v);
}
/// Signs the [payload] with a private key and returns the obtained
/// signature.
Future<MsgSignature> signToSignature(Uint8List payload, {int? chainId});
/// Signs an Ethereum specific signature. This method is equivalent to
/// [sign], but with a special prefix so that this method can't be used to
/// sign, for instance, transactions.
Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) {
final prefix = _messagePrefix + payload.length.toString();
final prefixBytes = ascii.encode(prefix);
// will be a Uint8List, see the documentation of Uint8List.+
final concat = uint8ListFromList(prefixBytes + payload);
return sign(concat, chainId: chainId);
}
}
/// Credentials where the [address] is known synchronously.
abstract class CredentialsWithKnownAddress extends Credentials {
/// The ethereum address belonging to this credential.
EthereumAddress get address;
@override
Future<EthereumAddress> extractAddress() async {
return Future.value(address);
}
}
/// Interface for [Credentials] that don't sign transactions locally, for
/// instance because the private key is not known to this library.
abstract class CustomTransactionSender extends Credentials {
Future<String> sendTransaction(Transaction transaction);
}
/// Credentials that can sign payloads with an Ethereum private key.
class EthPrivateKey extends CredentialsWithKnownAddress {
final Uint8List privateKey;
EthereumAddress? _cachedAddress;
EthPrivateKey(this.privateKey);
EthPrivateKey.fromHex(String hex) : privateKey = hexToBytes(hex);
/// Creates a new, random private key from the [random] number generator.
///
/// For security reasons, it is very important that the random generator used
/// is cryptographically secure. The private key could be reconstructed by
/// someone else otherwise. Just using [Random()] is a very bad idea! At least
/// use [Random.secure()].
factory EthPrivateKey.createRandom(Random random) {
final key = generateNewPrivateKey(random);
return EthPrivateKey(intToBytes(key));
}
@override
final bool isolateSafe = true;
@override
EthereumAddress get address {
return _cachedAddress ??= EthereumAddress(
publicKeyToAddress(privateKeyBytesToPublic(privateKey)));
}
@override
Future<MsgSignature> signToSignature(Uint8List payload,
{int? chainId}) async {
final signature = secp256k1.sign(keccak256(payload), privateKey);
// https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L26
// be aware that signature.v already is recovery + 27
final chainIdV =
chainId != null ? (signature.v - 27 + (chainId * 2 + 35)) : signature.v;
return MsgSignature(signature.r, signature.s, chainIdV);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is EthPrivateKey &&
runtimeType == other.runtimeType &&
const ListEquality().equals(privateKey, other.privateKey);
@override
int get hashCode => privateKey.hashCode;
}