Skip to content
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
6 changes: 5 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32"
/>
<!-- <uses-permission android:name="android.permission.NFC"/> -->
<uses-permission android:name="android.permission.NFC" />
<uses-feature
android:name="android.hardware.nfc"
android:required="false"
/>
<uses-feature
android:name="android.hardware.bluetooth"
android:required="false"
Expand Down
3 changes: 3 additions & 0 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = BX99T32YGS;
ENABLE_BITCODE = NO;
Expand Down Expand Up @@ -689,6 +690,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = BX99T32YGS;
ENABLE_BITCODE = NO;
Expand Down Expand Up @@ -718,6 +720,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
CURRENT_PROJECT_VERSION = 186;
DEVELOPMENT_TEAM = BX99T32YGS;
ENABLE_BITCODE = NO;
Expand Down
4 changes: 4 additions & 0 deletions ios/Runner/DebugProfile.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldnt this need to have the app id?

</array>
<key>keychain-access-groups</key>
<array/>
</dict>
Expand Down
6 changes: 6 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NFCReaderUsageDescription</key>
<string>Connect with your hardware wallet over NFC</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array/>
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array/>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand Down
4 changes: 4 additions & 0 deletions ios/Runner/Release.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
<string>TAG</string>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, app id?

</array>
<key>keychain-access-groups</key>
<array/>
</dict>
Expand Down
1 change: 1 addition & 0 deletions lib/core/entities/signer_device_entity.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ enum QrType { none, bbqr, urqr }
enum SignerDeviceEntity {
bitbox02,
coldcardQ,
coldcardMk4,
jade,
keystone,
krux,
Expand Down
3 changes: 3 additions & 0 deletions lib/core/storage/tables/wallet_metadata_table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum Signer {
enum SignerDevice {
bitbox02,
coldcardQ,
coldcardMk4,
jade,
keystone,
krux,
Expand All @@ -41,6 +42,7 @@ enum SignerDevice {
static SignerDevice fromEntity(SignerDeviceEntity entity) => switch (entity) {
SignerDeviceEntity.bitbox02 => SignerDevice.bitbox02,
SignerDeviceEntity.coldcardQ => SignerDevice.coldcardQ,
SignerDeviceEntity.coldcardMk4 => SignerDevice.coldcardMk4,
SignerDeviceEntity.jade => SignerDevice.jade,
SignerDeviceEntity.keystone => SignerDevice.keystone,
SignerDeviceEntity.krux => SignerDevice.krux,
Expand All @@ -56,6 +58,7 @@ enum SignerDevice {
SignerDeviceEntity toEntity() => switch (this) {
SignerDevice.bitbox02 => SignerDeviceEntity.bitbox02,
SignerDevice.coldcardQ => SignerDeviceEntity.coldcardQ,
SignerDevice.coldcardMk4 => SignerDeviceEntity.coldcardMk4,
SignerDevice.jade => SignerDeviceEntity.jade,
SignerDevice.keystone => SignerDeviceEntity.keystone,
SignerDevice.krux => SignerDeviceEntity.krux,
Expand Down
115 changes: 115 additions & 0 deletions lib/core/utils/nfc_payload_parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import 'dart:convert' show base64, utf8;

import 'package:convert/convert.dart';
import 'package:crypto/crypto.dart';
import 'package:ndef/ndef.dart' as ndef;

const _bitcoinNfcTypePrefix = 'urn:nfc:ext:';
const _bitcoinPsbtType = 'bitcoin.org:psbt';
const _bitcoinTxnType = 'bitcoin.org:txn';
const _bitcoinSha256Type = 'bitcoin.org:sha256';
const _jsonMimeType = 'application/json';

String? payloadFromNdefRecords(List<ndef.NDEFRecord> records) {
if (records.isEmpty) return null;

final bitcoinPayload = _payloadFromBitcoinExternalRecords(records);
if (bitcoinPayload != null || _hasBitcoinPayloadRecord(records)) {
return bitcoinPayload;
}

return _payloadFromRecord(records.last);
}

String? _payloadFromBitcoinExternalRecords(List<ndef.NDEFRecord> records) {
final txnPayload = _externalPayloadForType(records, _bitcoinTxnType);
if (txnPayload != null) {
if (!_matchesSha256Checksum(records, txnPayload)) return null;
return hex.encode(txnPayload);
}

final psbtPayload = _externalPayloadForType(records, _bitcoinPsbtType);
if (psbtPayload != null) {
if (!_matchesSha256Checksum(records, psbtPayload)) return null;
return base64.encode(psbtPayload);
}

return null;
}

bool _hasBitcoinPayloadRecord(List<ndef.NDEFRecord> records) {
return _hasExternalRecordForType(records, _bitcoinTxnType) ||
_hasExternalRecordForType(records, _bitcoinPsbtType);
}

String? _payloadFromRecord(ndef.NDEFRecord record) {
if (record is ndef.TextRecord) {
final text = record.text;
return text == null || text.isEmpty ? null : text;
}

if (record is ndef.ExternalRecord) {
final payload = record.payload;
if (payload == null || payload.isEmpty) return null;
return hex.encode(payload);
}

if (record is ndef.MimeRecord && record.decodedType == _jsonMimeType) {
final payload = record.payload;
if (payload == null || payload.isEmpty) return null;

try {
return utf8.decode(payload);
} on FormatException {
return null;
}
}

return null;
}

List<int>? _externalPayloadForType(List<ndef.NDEFRecord> records, String type) {
for (final record in records.whereType<ndef.ExternalRecord>()) {
if (_normalizedExternalType(record) != type) continue;

final payload = record.payload;
if (payload == null || payload.isEmpty) return null;
return payload;
}

return null;
}

bool _hasExternalRecordForType(List<ndef.NDEFRecord> records, String type) {
return records.whereType<ndef.ExternalRecord>().any(
(record) => _normalizedExternalType(record) == type,
);
}

String? _normalizedExternalType(ndef.ExternalRecord record) {
final decodedType = record.decodedType;
if (decodedType == null || decodedType.isEmpty) return null;

if (decodedType.startsWith(_bitcoinNfcTypePrefix)) {
return decodedType.substring(_bitcoinNfcTypePrefix.length);
}

return decodedType;
}

bool _matchesSha256Checksum(List<ndef.NDEFRecord> records, List<int> payload) {
final expectedHash = _externalPayloadForType(records, _bitcoinSha256Type);
if (expectedHash == null) return true;

return _bytesEqual(expectedHash, sha256.convert(payload).bytes);
}

bool _bytesEqual(List<int> left, List<int> right) {
if (left.length != right.length) return false;

for (var i = 0; i < left.length; i++) {
if (left[i] != right[i]) return false;
}

return true;
}
Loading