Skip to content
Draft
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
15 changes: 14 additions & 1 deletion lib/packages/wallet/eip712_signer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,20 @@ class Eip712Signer {
required bool swissTaxResidence,
required String registrationDate,
}) {
// The BitBox02 firmware refuses to sign typed data whose EIP712Domain has
// no chainId ("typed data has no chain ID" shown on the device), so
// hardware wallets sign with the chainId-extended domain. Software wallets
// keep the legacy chainId-less domain until Aktionariat has confirmed its
// signature re-verification accepts the extended variant — the API
// accepts both (pair PR DFXswiss/api#4354).
final includeChainIdInDomain = credentials is BitboxCredentials;

final Map<String, dynamic> typedDataMap = {
'types': {
'EIP712Domain': [
{'name': 'name', 'type': 'string'},
{'name': 'version', 'type': 'string'},
if (includeChainIdInDomain) {'name': 'chainId', 'type': 'uint256'},
],
'RealUnitUser': [
{'name': 'email', 'type': 'string'},
Expand All @@ -47,7 +56,11 @@ class Eip712Signer {
],
},
'primaryType': 'RealUnitUser',
'domain': {'name': 'RealUnitUser', 'version': '1'},
'domain': {
'name': 'RealUnitUser',
'version': '1',
if (includeChainIdInDomain) 'chainId': chainId,
},
'message': {
'email': email,
'name': name,
Expand Down
23 changes: 23 additions & 0 deletions test/packages/wallet/eip712_signer_bitbox_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:bitbox_flutter/bitbox_manager.dart';
Expand Down Expand Up @@ -48,6 +49,28 @@ void main() {
expect(await signRegistration(), '0xcafebabe');
});

// The BitBox02 firmware rejects typed data whose EIP712Domain has no
// chainId ("typed data has no chain ID" on the device) — hardware-wallet
// registrations must sign the chainId-extended domain. The software-wallet
// path keeps the legacy domain (pinned by the golden signature in
// eip712_signer_test.dart).
test('signs with the chainId-extended EIP-712 domain', () async {
when(
() => manager.signETHTypedMessage(any(), any(), any()),
).thenAnswer((_) async => Uint8List.fromList([0x01]));

await signRegistration();

final jsonMessage =
verify(() => manager.signETHTypedMessage(any(), any(), captureAny())).captured.single as Uint8List;
final typedData = jsonDecode(utf8.decode(jsonMessage)) as Map<String, dynamic>;
expect(typedData['domain']['chainId'], 1);
expect(
(typedData['types']['EIP712Domain'] as List).map((e) => e['name']),
containsAll(['name', 'version', 'chainId']),
);
});

test('throws SigningCancelledException on empty signature', () async {
when(
() => manager.signETHTypedMessage(any(), any(), any()),
Expand Down