Line data Source code
1 : import 'dart:convert';
2 : import 'dart:typed_data';
3 : import 'package:bip32/bip32.dart' as bip32;
4 : import 'package:hex/hex.dart';
5 : import 'package:walletkit_dart/src/crypto/utxo/utils/pubkey_to_address.dart';
6 : import 'package:walletkit_dart/walletkit_dart.dart';
7 : import 'package:pointycastle/src/utils.dart' as p_utils;
8 : import 'package:bip32/src/utils/ecurve.dart' as ecc;
9 : import 'package:convert/convert.dart' as convert;
10 :
11 0 : List<int> nomoIdDerivationPath(int hostId) {
12 0 : return [0, 100, 102, hostId];
13 : }
14 :
15 0 : String ec8Recover({required String message, required String sig}) {
16 0 : final messageHash = createEurocoinMessageHash(message);
17 0 : final parsedSig = _parseEc8Signature(sig);
18 0 : final pubKeyUncompressed = recoverPublicKey(messageHash, parsedSig);
19 :
20 0 : final uncompressedPrefix = [0x04];
21 : final pubKeyCompressed =
22 0 : compressPublicKey(Uint8List.fromList(uncompressedPrefix + pubKeyUncompressed));
23 0 : final pubKeyHex = convert.hex.encode(pubKeyCompressed);
24 : return pubKeyHex;
25 : }
26 :
27 0 : Signature _parseEc8Signature(String signature) {
28 0 : if (signature.startsWith("0x")) {
29 0 : throw WKFailure("expected to not begin with 0x");
30 : }
31 0 : if (signature.length != 130) {
32 0 : throw WKFailure("Unexpected signature length");
33 : }
34 :
35 : // Extract the r and s values
36 0 : BigInt r = BigInt.parse(signature.substring(2, 66), radix: 16);
37 0 : BigInt s = BigInt.parse(signature.substring(66, 130), radix: 16);
38 :
39 : // Extract the recovery identifier (v) and make it backwards-compat to the following C-code:
40 : // int expectedRecId = (((uint8_t *)compactSig)[0] - 27) % 4;
41 0 : BigInt v = BigInt.parse(signature.substring(0, 2), radix: 16);
42 0 : final expectedRecId = (v - BigInt.from(27)) % BigInt.from(4);
43 0 : v = expectedRecId + BigInt.from(27);
44 :
45 0 : return Signature.fromRSV(r, s, v.toInt());
46 : }
47 :
48 0 : Uint8List createEurocoinMessageHash(String message) {
49 : List<int> messageBytes;
50 0 : if (message.startsWith("0x")) {
51 0 : messageBytes = HEX.decode(message.substring(2));
52 : } else {
53 0 : messageBytes = utf8.encode(message);
54 : }
55 0 : final varIntLength = encodeVarint(messageBytes.length);
56 0 : final prefix = utf8.encode('\u0019Eurocoin Signed Message:\n') + varIntLength;
57 0 : final hashInput = Uint8List.fromList(prefix + messageBytes);
58 :
59 0 : return sha256Sha256Hash(hashInput);
60 : }
61 :
62 0 : Uint8List ec8SignMessage(String message, NodeWithAddress node) {
63 0 : final privateKey = node.bip32Node!.privateKey!;
64 : // message test vector: https://zeniq.id/safir.com/backend/qrl?n=2f24bc32c0752e835e21&r=/backend/qrll
65 0 : final messageHash = createEurocoinMessageHash(message);
66 : // messageHash test vector: u8 uint8_t[32] "\xf9\x9b\xf9~\U00000015j\xf1\xd1K\U0000001a\U00000002[\U00000011\x83\U0000001ae\xeb\nH\xa0zⴞ\xa8k\xc4Tn~\x80\xe3"
67 0 : final signature = Signature.createSignature(messageHash, privateKey, hashPayload: false);
68 :
69 0 : final r = padUint8ListTo32(unsignedIntToBytes(signature.r));
70 0 : final s = padUint8ListTo32(unsignedIntToBytes(signature.s));
71 0 : final v = unsignedIntToBytes(BigInt.from(signature.v));
72 :
73 : // https://github.com/ethereumjs/ethereumjs-util/blob/8ffe697fafb33cefc7b7ec01c11e3a7da787fe0e/src/signature.ts#L63
74 0 : return uint8ListFromList(v + r + s);
75 : }
76 :
77 0 : String _derivationPathToString(List<int> derivationPath) {
78 : String path = "m";
79 0 : for (final rawInt in derivationPath) {
80 : int i = rawInt;
81 : bool hardened = false;
82 0 : if (rawInt < 0) {
83 0 : i += 0x80000000;
84 : hardened = true;
85 0 : } else if (rawInt >= 0x80000000) {
86 0 : i -= 0x80000000;
87 : hardened = true;
88 : }
89 0 : path += "/" + i.toString();
90 : if (hardened) {
91 0 : path += "'";
92 : }
93 : }
94 : return path;
95 : }
96 :
97 0 : Future<NodeWithAddress> deriveBIP32Ec8({
98 : required List<int> derivationPath,
99 : required Uint8List seed,
100 : }) async {
101 0 : final String derivationPathString = _derivationPathToString(derivationPath);
102 :
103 0 : bip32.BIP32 seedNode = bip32.BIP32.fromSeed(seed);
104 0 : final bip32.BIP32 childNode = seedNode.derivePath(derivationPathString);
105 :
106 : const compressed = false; // needed for address backwards compat
107 0 : final publicKey = ecc.pointFromScalar(childNode.privateKey!, compressed)!;
108 0 : final address = pubKeyToAddress(
109 : publicKey,
110 : AddressType.legacy,
111 : EurocoinNetwork,
112 : );
113 :
114 0 : return ChangeNode(
115 : bip32Node: childNode,
116 : address: address,
117 : derivationPath: "", //
118 0 : addresses: {AddressType.legacy: address},
119 : walletPurpose: HDWalletPurpose.BIP44,
120 0 : publicKey: childNode.publicKey.toHex,
121 : );
122 : }
123 :
124 9 : Uint8List uint8ListFromList(List<int> data) {
125 9 : if (data is Uint8List) return data;
126 :
127 1 : return Uint8List.fromList(data);
128 : }
129 :
130 4 : Uint8List padUint8ListTo32(Uint8List data) {
131 12 : assert(data.length <= 32);
132 8 : if (data.length == 32) return data;
133 :
134 0 : return Uint8List(32)..setRange(32 - data.length, 32, data);
135 : }
136 :
137 2 : Uint8List unsignedIntToBytes(BigInt number) {
138 4 : assert(!number.isNegative);
139 2 : return p_utils.encodeBigIntAsUnsigned(number);
140 : }
141 :
142 0 : Uint8List encodeVarint(int value) {
143 0 : final List<int> result = [];
144 :
145 0 : if (value < 0xFD) {
146 0 : result.add(value);
147 0 : } else if (value <= 0xFFFF) {
148 0 : result.addAll([0xFD, value & 0xFF, (value >> 8) & 0xFF]);
149 0 : } else if (value <= 0xFFFFFFFF) {
150 0 : result.addAll(
151 0 : [0xFE, value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]);
152 : } else {
153 0 : result.addAll([
154 : 0xFF,
155 0 : value & 0xFF,
156 0 : (value >> 8) & 0xFF,
157 0 : (value >> 16) & 0xFF,
158 0 : (value >> 24) & 0xFF,
159 0 : (value >> 32) & 0xFF,
160 0 : (value >> 40) & 0xFF,
161 0 : (value >> 48) & 0xFF,
162 0 : (value >> 56) & 0xFF,
163 : ]);
164 : }
165 0 : return Uint8List.fromList(result);
166 : }
|