Skip to content

Commit 5d5883e

Browse files
committed
Replace bitcoindart and coinlib_flutter with the coin library
Migrate all UTXO key derivation, address generation, network parameters, transaction building, signing, and message signing in the wallet engine from bitcoindart/coinlib_flutter to the coin library, added as a submodule under crypto_plugins/coin and wired as a path dependency. Coin definitions and the Bip39HD currency/wallet bases now use coin.Chain and coin key/address/script types. The ElectrumX, PayNym, Spark, MWEB, CashFusion, and extended-keys interfaces are restructured onto coin sign-then-assemble transaction building. PayNym gains P2TR payment code support. Particl retains its witness-only signing format, and Firo ProReg collateral signing moves to coin MessageSig. Fork bip47 and fusiondart locally under crypto_plugins so their internals use coin, and drop the direct bitcoindart and coinlib_flutter dependencies and overrides. Add bitcoindart-coverage and coin equivalence/regression test suites.
1 parent 51db6c7 commit 5d5883e

152 files changed

Lines changed: 26449 additions & 1666 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yaml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,10 @@ jobs:
316316
CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
317317
run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
318318

319-
- name: Build secp256k1.dll for Windows
320-
run: dart run coinlib:build_windows
319+
# TODO: provide secp256k1.dll for Windows. coinlib was removed and the
320+
# coin package (crypto_plugins/coin) ships no build_windows command. The
321+
# coin FFI loader expects build/secp256k1.dll or secp256k1.dll on PATH;
322+
# restore a step that produces it before relying on the native backend.
321323

322324
- name: Build
323325
run: flutter build windows --release
@@ -914,8 +916,10 @@ jobs:
914916
CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
915917
run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
916918

917-
- name: Build secp256k1.dll for Windows
918-
run: dart run coinlib:build_windows
919+
# TODO: provide secp256k1.dll for Windows. coinlib was removed and the
920+
# coin package (crypto_plugins/coin) ships no build_windows command. The
921+
# coin FFI loader expects build/secp256k1.dll or secp256k1.dll on PATH;
922+
# restore a step that produces it before relying on the native backend.
919923

920924
- name: Build
921925
run: flutter build windows --release
@@ -1372,8 +1376,10 @@ jobs:
13721376
CHANGE_NOW: ${{ secrets.CHANGE_NOW }}
13731377
run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart
13741378

1375-
- name: Build secp256k1.dll for Windows
1376-
run: dart run coinlib:build_windows
1379+
# TODO: provide secp256k1.dll for Windows. coinlib was removed and the
1380+
# coin package (crypto_plugins/coin) ships no build_windows command. The
1381+
# coin FFI loader expects build/secp256k1.dll or secp256k1.dll on PATH;
1382+
# restore a step that produces it before relying on the native backend.
13771383

13781384
- name: Build
13791385
run: flutter build windows --release

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "crypto_plugins/flutter_libmwc"]
88
path = crypto_plugins/flutter_libmwc
99
url = https://github.com/cypherstack/flutter_libmwc
10+
[submodule "crypto_plugins/coin"]
11+
path = crypto_plugins/coin
12+
url = git@github.com:ManyMath/coin

crypto_plugins/bip47/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0
2+
3+
- Initial version.

crypto_plugins/bip47/LICENSE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2023 Cypher Stack LLC
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6+
7+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

crypto_plugins/bip47/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# BIP47 version 1
2+
https://github.com/OpenBitcoinPrivacyProject/bips/blob/master/bip-0047.mediawiki#version-1
3+
4+
Simple v1 BIP47 functionality in Dart
5+
6+
Based mainly on the following:
7+
https://github.com/sparrowwallet/drongo/tree/master/src/main/java/com/sparrowwallet/drongo/bip47
8+
https://github.com/Samourai-Wallet/ExtLibJ/tree/3b9832954d3754baaca3307ecc6a872f5aa5d9bc/java/com/samourai/wallet/bip47
9+
10+
Note: Use at your own risk
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# This file configures the static analysis results for your project (errors,
2+
# warnings, and lints).
3+
#
4+
# This enables the 'recommended' set of lints from `package:lints`.
5+
# This set helps identify many issues that may lead to problems when running
6+
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
7+
# style and format.
8+
#
9+
# If you want a smaller set of lints you can change this to specify
10+
# 'package:lints/core.yaml'. These are just the most critical lints
11+
# (the recommended set includes the core lints).
12+
# The core lints are also what is used by pub.dev for scoring packages.
13+
14+
include: package:lints/recommended.yaml
15+
16+
# Uncomment the following section to specify additional rules.
17+
18+
# linter:
19+
# rules:
20+
# - camel_case_types
21+
22+
# analyzer:
23+
# exclude:
24+
# - path/to/excluded/files/**
25+
26+
# For more information about the core and recommended set of lints, see
27+
# https://dart.dev/go/core-lints
28+
29+
# For additional information about configuring this file, see
30+
# https://dart.dev/guides/language/analysis-options
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/// Support for doing something awesome.
2+
///
3+
/// More dartdocs go here.
4+
library bip47;
5+
6+
export 'src/bip47_base.dart';
7+
8+
// TODO: Export any libraries intended for clients of this package.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export 'network_type.dart';
2+
export 'payment_address.dart';
3+
export 'payment_code.dart';
4+
export 'secret_point.dart';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/// Lightweight network type definition for BIP-47 address generation.
2+
///
3+
/// This replaces the bitcoindart NetworkType dependency. Fields match the
4+
/// bitcoindart API so callers can migrate with minimal changes.
5+
class Bip32Type {
6+
final int public;
7+
final int private;
8+
9+
const Bip32Type({required this.public, required this.private});
10+
}
11+
12+
class NetworkType {
13+
final String? messagePrefix;
14+
final String? bech32;
15+
final Bip32Type bip32;
16+
final int pubKeyHash;
17+
final int scriptHash;
18+
final int wif;
19+
20+
const NetworkType({
21+
this.messagePrefix,
22+
this.bech32,
23+
required this.bip32,
24+
required this.pubKeyHash,
25+
required this.scriptHash,
26+
required this.wif,
27+
});
28+
}
29+
30+
const bitcoin = NetworkType(
31+
messagePrefix: '\x18Bitcoin Signed Message:\n',
32+
bech32: 'bc',
33+
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
34+
pubKeyHash: 0x00,
35+
scriptHash: 0x05,
36+
wif: 0x80,
37+
);
38+
39+
const testnet = NetworkType(
40+
messagePrefix: '\x18Bitcoin Signed Message:\n',
41+
bech32: 'tb',
42+
bip32: Bip32Type(public: 0x043587cf, private: 0x04358394),
43+
pubKeyHash: 0x6f,
44+
scriptHash: 0xc4,
45+
wif: 0xef,
46+
);
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import 'dart:typed_data';
2+
3+
import 'package:bip32/bip32.dart' hide NetworkType, Bip32Type;
4+
import 'package:bip47/src/network_type.dart';
5+
import 'package:bip47/src/payment_code.dart';
6+
import 'package:bip47/src/secret_point.dart';
7+
import 'package:bip47/src/util.dart';
8+
import 'package:coin/coin.dart' as coin;
9+
import 'package:pointycastle/digests/sha256.dart';
10+
import 'package:pointycastle/pointycastle.dart';
11+
12+
class PaymentAddress {
13+
final PaymentCode paymentCode;
14+
final BIP32? bip32Node;
15+
final NetworkType networkType;
16+
int index;
17+
18+
static final curveParams = ECDomainParameters("secp256k1");
19+
20+
PaymentAddress({
21+
required this.paymentCode,
22+
this.bip32Node,
23+
NetworkType? networkType,
24+
this.index = 0,
25+
}) : networkType = networkType ?? bitcoin;
26+
27+
ECPoint sG() => (curveParams.G * getSecretPoint())!;
28+
29+
SecretPoint getSharedSecret() => SecretPoint(
30+
bip32Node!.privateKey!,
31+
paymentCode.derivePublicKey(index),
32+
);
33+
34+
BigInt getSecretPoint() {
35+
// convert hash to value 's'
36+
final BigInt s = hashSharedSecret().toBigInt;
37+
38+
// check that 's' is a member of the associated scalar group
39+
if (!s.isScalarGroupMemberOf(curveParams)) {
40+
throw Exception("Secret point is not a member of the secp256k1 group");
41+
}
42+
43+
return s;
44+
}
45+
46+
ECPoint getECPoint() =>
47+
curveParams.curve.decodePoint(paymentCode.derivePublicKey(index))!;
48+
49+
Uint8List hashSharedSecret() =>
50+
SHA256Digest().process(getSharedSecret().ecdhSecret());
51+
52+
Uint8List _getSendAddressPublicKey() {
53+
final sum = getECPoint() + sG();
54+
return sum!.getEncoded(true);
55+
}
56+
57+
String getSendAddressP2PKH() {
58+
final pubKeyBytes = _getSendAddressPublicKey();
59+
final pkHash = coin.hash160(pubKeyBytes);
60+
return coin.P2pkhAddr(pkHash).encode(_toCoinChain());
61+
}
62+
63+
String getSendAddressP2WPKH() {
64+
final pubKeyBytes = _getSendAddressPublicKey();
65+
final pkHash = coin.hash160(pubKeyBytes);
66+
return coin.P2wpkhAddr(pkHash).encode(_toCoinChain());
67+
}
68+
69+
String getSendAddressP2TR() {
70+
// Step 1: BIP-47 point addition -> compressed pubkey B'
71+
final bip47PubKey = _getSendAddressPublicKey();
72+
73+
// Step 2: BIP-341 taproot key-path tweak (no script tree)
74+
final internalKey = coin.PublicKey(bip47PubKey);
75+
final outputKey = coin.Taproot(internalKey: internalKey).tweakedKey;
76+
77+
// Step 3: bech32m address encoding
78+
return coin.TaprootAddr(outputKey).encode(_toCoinChain());
79+
}
80+
81+
/// Returns the tweaked private key and derived public key for receiving.
82+
///
83+
/// The returned record has [privateKey] (raw 32 bytes) and [publicKey]
84+
/// (compressed 33 bytes). This replaces the old bitcoindart ECPair.
85+
({Uint8List privateKey, Uint8List publicKey}) getReceiveAddressKeyPair() {
86+
final tweakedPrivKey = _addSecp256k1(
87+
bip32Node!.privateKey!.toBigInt,
88+
getSecretPoint(),
89+
).to32Bytes();
90+
91+
final sk = coin.SecretKey(tweakedPrivKey);
92+
return (privateKey: tweakedPrivKey, publicKey: sk.publicKey.bytes);
93+
}
94+
95+
String getReceiveAddressP2PKH() {
96+
final pair = getReceiveAddressKeyPair();
97+
final pkHash = coin.hash160(pair.publicKey);
98+
return coin.P2pkhAddr(pkHash).encode(_toCoinChain());
99+
}
100+
101+
String getReceiveAddressP2WPKH() {
102+
final pair = getReceiveAddressKeyPair();
103+
final pkHash = coin.hash160(pair.publicKey);
104+
return coin.P2wpkhAddr(pkHash).encode(_toCoinChain());
105+
}
106+
107+
String getReceiveAddressP2TR() {
108+
// Step 1: BIP-47 scalar addition -> keypair (b', B')
109+
final pair = getReceiveAddressKeyPair();
110+
111+
// Step 2: BIP-341 taproot key-path tweak using B' as internal key
112+
final internalKey = coin.PublicKey(pair.publicKey);
113+
final outputKey = coin.Taproot(internalKey: internalKey).tweakedKey;
114+
115+
// Step 3: bech32m address encoding
116+
return coin.TaprootAddr(outputKey).encode(_toCoinChain());
117+
}
118+
119+
BigInt _addSecp256k1(BigInt b1, BigInt b2) {
120+
final BigInt value = b1 + b2;
121+
122+
return value % curveParams.n;
123+
}
124+
125+
coin.Chain _toCoinChain() {
126+
return coin.Chain(
127+
wifPrefix: networkType.wif,
128+
p2pkhPrefix: networkType.pubKeyHash,
129+
p2shPrefix: networkType.scriptHash,
130+
bech32Hrp: networkType.bech32,
131+
name: 'bip47',
132+
bip44CoinType: 0,
133+
privHDPrefix: networkType.bip32.private,
134+
pubHDPrefix: networkType.bip32.public,
135+
);
136+
}
137+
}

0 commit comments

Comments
 (0)