Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sign typed data and isvalidsignature methods #18

Open
wants to merge 1 commit into
base: fix/eoa_sig_options
Choose a base branch
from
Open
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: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.1.9

- Add support for SignTypedData for signers
- Add ERC-1271 - isValidSignature for signers
- fix incosistent b64 credential encoding in passkeypair and passkeysignature

## 0.1.8

- fix hardcoded auth attachment
Expand Down
4 changes: 3 additions & 1 deletion lib/src/interfaces/interfaces.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ library interfaces;

import 'dart:typed_data';

import 'package:eth_sig_util/eth_sig_util.dart';
import 'package:passkeys/authenticator.dart' show PasskeyAuthenticator;
import 'package:passkeys/types.dart';
import 'package:web3_signers/src/vendor/vendor.dart' show U8aExtension;
import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';

import '../utils/utils.dart' show Uint256, hexlify;
import '../utils/utils.dart'
show ERC1271IsValidSignatureResponse, Uint256, hexlify;
import '../web3_signers_base.dart' show PassKeyPair, PassKeySignature;

part 'eoa_wallet_interface.dart';
Expand Down
52 changes: 52 additions & 0 deletions lib/src/interfaces/multi_signer_interface.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: constant_identifier_names

part of 'interfaces.dart';

typedef MSI = MultiSignerInterface;
Expand Down Expand Up @@ -62,4 +64,54 @@ abstract class MultiSignerInterface {
/// final signature = await signToEc(hash, 0);
/// ```
Future<MsgSignature> signToEc(Uint8List hash, {int? index});

/// Signs typed data according to EIP-712 standard.
///
/// Parameters:
/// - [jsonData]: The typed data in JSON format that needs to be signed.
/// - [version]: The version of the typed data standard to use for signing.
/// - [index] optianal value to pass to the function.
/// - can be index to specify which privatekey to use for signing (required for HD wallets).
///
/// Example:
/// ```dart
/// final jsonData = '''
/// {
/// "types": {
/// "EIP712Domain": [...],
/// "Mail": [...]
/// },
/// "primaryType": "Mail",
/// "domain": {...},
/// "message": {...}
/// }
/// ''';
/// final signature = await signTypedData(jsonData, TypedDataVersion.V4);
/// ```
Future<Uint8List> signTypedData(String jsonData, TypedDataVersion version,
{int? index});

/// {@template isValidSignature}
/// Verifies a personal signature against a hash.
///
/// Parameters:
/// - [hash]: The original hash that was signed.
/// - [signature]: The signature to verify against the hash.
/// - [signer]: The signer address or public key to verify the signature against.
/// {@endtemplate}
///
/// The signer parameter is generic [T] to support different signer types.
/// This is used to specify the publickey to verify the signature against.
/// and can be the signer address or passkey keypair.
/// The signature parameter is also generic [U] to support different signature formats.
/// Returns [ERC1271IsValidSignatureResponse] magic value.
///
/// Example:
/// ```dart
/// final hash = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]);
/// final signature = await personalSign(hash);
/// final isValid = await isValidSignature<bool, Uint8List>(hash, signature);
/// ```
Future<ERC1271IsValidSignatureResponse> isValidSignature<T, U>(
Uint8List hash, U signature, T signer);
}
34 changes: 34 additions & 0 deletions lib/src/interfaces/passkey_signer_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,40 @@ abstract class PasskeySignerInterface extends MultiSignerInterface {
Future<PassKeySignature> signToPasskeySignature(Uint8List hash,
{List<CredentialType>? knownCredentials});

/// {@macro isValidSignature}
/// - [p256Verifier]: The public key of the P256 verifier.
/// - [rpcUrl]: The URL of the Ethereum JSON-RPC endpoint.
///
/// Returns a Future<ERC1271IsValidSignatureResponse> representing the validity of the signature.
///
/// Example:
/// ```dart
/// final hash = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]);
/// final signature = await signToPasskeySignature(hash);
/// final isValid = await isValidPassKeySignature(hash, signature, keypair, p256Verifier, rpcUrl);
/// ```
Future<ERC1271IsValidSignatureResponse> isValidPassKeySignature(
Uint8List hash,
PassKeySignature signature,
PassKeyPair keypair,
EthereumAddress p256Verifier,
String rpcUrl);

/// Converts a PassKeySignature to an Safe Smart Account verifiable signature.
///
/// Parameters:
/// - [signature]: The PassKeySignature to be converted.
///
/// Returns an FCLSignature representing the converted signature.
///
/// Example:
/// ```dart
/// final hash = Uint8List.fromList([0x01, 0x02, 0x03, 0x04]);
/// final signature = await signToPasskeySignature(hash);
/// final fclSignature = passkeySignatureToFCLSignature(signature);
/// ```
FCLSignature passkeySignatureToFCLSignature(PassKeySignature signature);

/// Generates a random base64 string.
String randomBase64String();
}
25 changes: 25 additions & 0 deletions lib/src/signers/eoa_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,31 @@ class EOAWallet implements EOAWalletInterface {
final hdKey = _getHdKey(index);
return _deriveEthPrivKey(hdKey.key.toHex());
}

@override
Future<Uint8List> signTypedData(String jsonData, TypedDataVersion version,
{int? index}) {
final hash =
TypedDataUtil.hashMessage(jsonData: jsonData, version: version);
return personalSign(hash, index: index);
}

@override
Future<ERC1271IsValidSignatureResponse> isValidSignature<T, U>(
Uint8List hash, U signature, T address) {
require(signature is Uint8List || signature is MsgSignature,
'Signature must be of type Uint8List or MsgSignature');
require(
address is EthereumAddress, 'Address must be of type EthereumAddress');
address as EthereumAddress;
if (signature is Uint8List) {
return Future.value(isValidPersonalSignature(hash, signature, address));
} else {
final signer = ecRecover(keccak256(hash), signature as MsgSignature);
return Future.value(ERC1271IsValidSignatureResponse.isValid(
EthereumAddress.fromPublicKey(signer).hex == address.hex));
}
}
}

enum WordLength {
Expand Down
65 changes: 61 additions & 4 deletions lib/src/signers/passkey_signer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ class PassKeySignature {
/// final Uint8List encodedSig = pkpSig.toUint8List();
/// ```
Uint8List toUint8List() {
final cdjRgExp = RegExp(
r'^\{"type":"webauthn.get","challenge":"[A-Za-z0-9\-_]{43}",(.*)\}$');
final match = cdjRgExp.firstMatch(clientDataJSON)!;
return abi.encode([
'bytes',
Expand Down Expand Up @@ -204,6 +202,65 @@ class PassKeySigner implements PasskeySignerInterface {
assertion.userHandle);
}

@override
Future<Uint8List> signTypedData(String jsonData, TypedDataVersion version,
{int? index}) {
final hash =
TypedDataUtil.hashMessage(jsonData: jsonData, version: version);
return personalSign(hash);
}

@override
Future<ERC1271IsValidSignatureResponse> isValidPassKeySignature(
Uint8List hash,
PassKeySignature signature,
PassKeyPair keypair,
EthereumAddress p256Verifier,
String rpcUrl) async {
final hashBase64 = b64e(hash);
final clientDataJSON =
'{"type":"webauthn.get","challenge":"$hashBase64",${cdjRgExp.firstMatch(signature.clientDataJSON)![1]}}';
final clientHash = sha256Hash(utf8.encode(clientDataJSON));
final sigHash =
sha256Hash(signature.authData.concat(Uint8List.fromList(clientHash)));
final calldata = abi.encode([
"uint256",
"uint256",
"uint256",
"uint256",
"uint256"
], [
bytesToInt(sigHash),
signature.signature.item1.value,
signature.signature.item2.value,
keypair.authData.publicKey.item1.value,
keypair.authData.publicKey.item2.value
]);
final result = await p256Verify(calldata, p256Verifier.hex, rpcUrl);
return ERC1271IsValidSignatureResponse.isValidResult(result);
}

@override
@Deprecated("use 'isValidPassKeySignature' instead")
Future<ERC1271IsValidSignatureResponse> isValidSignature<T, U>(
Uint8List hash, U signature, T signer) {
if (signature is PassKeySignature && signer is PassKeyPair) {
final defaultP256Verifier = EthereumAddress.fromHex("0x${'0' * 37}100");
final defaultRpcUrl = "https://rpc.ankr.com/eth";
return isValidPassKeySignature(
hash, signature, signer, defaultP256Verifier, defaultRpcUrl);
} else {
throw ArgumentError(
"signature and signer must be of type PassKeySignature and PassKeyPair respectively, we recommend using 'isValidPassKeySignature' from `PasskeySignerInterface` instead");
}
}

@override
FCLSignature passkeySignatureToFCLSignature(PassKeySignature signature) {
return _buildSafeSignatureBytes(
_opts.sharedWebauthnSigner, signature.toUint8List());
}

@override
Future<PassKeyPair> register(String username, String displayname,
{String? challenge}) async {
Expand Down Expand Up @@ -251,8 +308,8 @@ class PassKeySigner implements PasskeySignerInterface {
final x = Uint256.fromHex(hexlify(keyX.value.value));
final y = Uint256.fromHex(hexlify(keyY.value.value));

return AuthData(base64Url.encode(credentialId),
Uint8List.fromList(credentialId), Tuple(x, y), aaGUID);
return AuthData(b64e(credentialId), Uint8List.fromList(credentialId),
Tuple(x, y), aaGUID);
}

AuthData _decodeAttestation(RegisterResponseType attestation) {
Expand Down
25 changes: 25 additions & 0 deletions lib/src/signers/private_key_signer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,29 @@ class PrivateKeySigner implements MultiSignerInterface {
"${hexlify(_options.prefix)}fffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c";

String toJson() => _credential.toJson();

@override
Future<Uint8List> signTypedData(String jsonData, TypedDataVersion version,
{int? index}) {
final hash =
TypedDataUtil.hashMessage(jsonData: jsonData, version: version);
return personalSign(hash);
}

@override
Future<ERC1271IsValidSignatureResponse> isValidSignature<T, U>(
Uint8List hash, U signature, T address) {
require(signature is Uint8List || signature is MsgSignature,
'Signature must be of type Uint8List or MsgSignature');
require(
address is EthereumAddress, 'Address must be of type EthereumAddress');
address as EthereumAddress;
if (signature is Uint8List) {
return Future.value(isValidPersonalSignature(hash, signature, address));
} else {
final signer = ecRecover(keccak256(hash), signature as MsgSignature);
return Future.value(ERC1271IsValidSignatureResponse.isValid(
EthereumAddress.fromPublicKey(signer).hex == address.hex));
}
}
}
31 changes: 31 additions & 0 deletions lib/src/utils/1271.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
part of 'utils.dart';

enum ERC1271IsValidSignatureResponse {
sucess("0x1626ba7e"),
failure("0xffffffff");

final String value;

const ERC1271IsValidSignatureResponse(this.value);

factory ERC1271IsValidSignatureResponse.isValid(bool value) {
return value ? sucess : failure;
}

factory ERC1271IsValidSignatureResponse.isValidResult(Uint256 result) {
return result.value == BigInt.one ? sucess : failure;
}
}

ERC1271IsValidSignatureResponse isValidPersonalSignature(
Uint8List message, Uint8List signature, EthereumAddress address) {
final prefix = '\u0019Ethereum Signed Message:\n${message.length}';
final prefixBytes = ascii.encode(prefix);
final payload = prefixBytes.concat(message);
final signer = ecRecover(
keccak256(payload),
MsgSignature(bytesToInt(signature.sublist(0, 32)),
bytesToInt(signature.sublist(32, 64)), signature[64]));
return ERC1271IsValidSignatureResponse.isValid(
EthereumAddress.fromPublicKey(signer).hex == address.hex);
}
Loading