// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; /// @title P256Owner — an ATECC608B chip as a Safe owner (GH-W1-001 §2.1, minimal ERC-1271 signer calling RIP-7212 at 0x100). /// @notice The chip signs, through the A3 signing broker, ECDSA-P256 over sha256(material). For a Safe transaction the /// material is the ASCII hex string of the Safe transaction hash (lower-case, 0x-prefixed). The broker runs in /// digest mode: the chip signs ECDSA-SHA256 over the 64-char ASCII hex of sha256(material), so this contract /// re-derives digest = sha256(hex64(sha256("0x" + hex(safeTxHash)))) and asks 0x100 to verify (digest, r, s, qx, qy). /// No key material lives here: only the chip's public key, immutable at deployment. contract P256Owner { bytes4 internal constant MAGIC_1271 = 0x1626ba7e; // isValidSignature(bytes32,bytes) bytes4 internal constant MAGIC_1271_LEGACY = 0x20c13b0b; // isValidSignature(bytes,bytes) address internal constant P256_VERIFIER = address(0x100); // RIP-7212 on Robinhood Chain 4663 uint256 internal constant N_HALF = 0x7FFFFFFF800000007FFFFFFFFFFFFFFFDE737D56D38BCF4279DCE5617E3192A8; uint256 public immutable qx; uint256 public immutable qy; string public chipSerial; constructor(uint256 _qx, uint256 _qy, string memory _serial) { qx = _qx; qy = _qy; chipSerial = _serial; } /// @dev Safe 1.4.1 calls isValidSignature(bytes32 dataHash, bytes signature) for contract owners (v == 0). function isValidSignature(bytes32 dataHash, bytes calldata signature) external view returns (bytes4) { return _verify(dataHash, signature) ? MAGIC_1271 : bytes4(0); } /// @dev Safe 1.3.0 style: isValidSignature(bytes data, bytes signature) where data is the pre-image. function isValidSignature(bytes calldata data, bytes calldata signature) external view returns (bytes4) { return _verify(keccak256(data), signature) ? MAGIC_1271_LEGACY : bytes4(0); } /// @notice Public so anyone can re-check a chip signature against this owner off the Safe path. function verify(bytes32 dataHash, bytes calldata signature) external view returns (bool) { return _verify(dataHash, signature); } function _verify(bytes32 dataHash, bytes calldata signature) internal view returns (bool) { if (signature.length != 64) return false; uint256 r = uint256(bytes32(signature[0:32])); uint256 s = uint256(bytes32(signature[32:64])); if (s > N_HALF) return false; // low-s only; the broker normalises, refuse otherwise bytes32 inner = sha256(bytes(_hex(dataHash, true))); // sha256 of the material "0x" bytes32 digest = sha256(bytes(_hex(inner, false))); // the chip signed SHA-256 over the 64-char hex of that (bool ok, bytes memory out) = P256_VERIFIER.staticcall(abi.encodePacked(digest, r, s, qx, qy)); return ok && out.length == 32 && abi.decode(out, (uint256)) == 1; } function _hex(bytes32 h, bool prefixed) internal pure returns (string memory) { bytes16 alphabet = 0x30313233343536373839616263646566; uint256 o = prefixed ? 2 : 0; bytes memory str = new bytes(64 + o); if (prefixed) { str[0] = "0"; str[1] = "x"; } for (uint256 i = 0; i < 32; i++) { str[o + i * 2] = alphabet[uint8(h[i] >> 4)]; str[o + 1 + i * 2] = alphabet[uint8(h[i] & 0x0f)]; } return string(str); } }