PileumConnect
π https://connect.pileum.eu
Users willing to use this service need :
- Mobile application FranceIdentitΓ© for Android or iPhone.
- French electronic national identity card :
Overviewβ
PileumConnect is a public OpenID Connect Provider
designed for proof of personhood & privacy.
Users
must own a french electronic national identity card to register an account and prove their identity.
The user's identity is processed directly in the browser, ensuring it is never transmitted over the internet. Accounts must be re-verified every year.
Any OpenID Connect Relying Party
can use the service to authenticate its users : the only user information provided is a Pairwise Pseudonymous Identifier (PPID). PPID is the identifier of an User
to a Relying Party
that cannot be correlated with the User
's PPID at another Relying Party
.
User verification procedureβ
Users must provide an identity attestation (which is PDF file) electronically-signed by the french government. This PDF is processed in the browser, and a Zero Knowledge proof is created to prove personhood to PileumConnect, without disclosing user identity.
FAQβ
Is this service affiliated with the government ?β
No.
How is processed my identity attestation ?β
Your identity (extracted from the PDF attestation file) is never transmitted over the Internet. Instead, your core identity (names, birth date, birth place) is processed directly in your browser to produce a unique identifier.
On our end, we only store the association between your passkey and your identifier.
Why is this service not offered on-chain (i.e. as a smart contract) ?β
For privacy reason : an attacker could brute-force identifiers published on-chain to reveal real identities. We made multiple attempts at the issuer level advocating for changes that would allow a full on-chain solution, but no luck so far.
How to recover my account ?β
You can recover your account by going through the verification procedure again.
Why going through the verification procedure every year ?β
To ensure that you still exist down here.
Developers (OIDC RP)β
Any Relying Party
can rely on PileumConnect to authenticate its users. Current service configuration is available at :
The only supported OIDC mode is Implicit flow, with the following parameters :
response_type
: id_token or eip712scope
: openid
Authentication Requestβ
When the RP wishes to Authenticate the End-User, the Client prepares an Authentication Request to be sent to the Authorization Endpoint. This request uses the Implicit flow with the following parameters:
client_id=5eb4e6846e1e31c668486dec12762dc2e406398bb5d14bef329fff4a9ce95b75
&response_type=id_token
&redirect_uri=https%3A%2F%2Frelying-party.fr%2Fcallback
&scope=openid
&state=c3a7fbe63294f6f4fedce7dc5fa29b3c
&nonce=1be0695758d9ad41e676865558739b35
client_id
: SHA256 hash ofredirect_uri
hostname.
Authentication Responseβ
If the End-User authenticates successfully & grants the request, the Client is redirected to redirect_uri
:
state=c3a7fbe63294f6f4fedce7dc5fa29b3c
&id_token=eyJhbGciOiJ[...]
Parameter id_token
is a JSON Web Token (JWT) signed with ES256 :
{
"alg" : "ES256",
"kid" : "oidc"
}
{
"aud" : "5eb4e6846e1e31c668486dec12762dc2e406398bb5d14bef329fff4a9ce95b75",
"exp" : 1708482941,
"iat" : 1708396549,
"iss" : "https://connect.pileum.eu",
"nonce" : "1be0695758d9ad41e676865558739b35",
"sub" : "aade285ad68dba3bc48a1e129e3992b5fb52e6e84480eb69107adce256277acc"
}
Property sub
is the PPID, derived from redirect_uri
hostname.
Web3 : EIP-712β
This feature is custom and is not supported by the OpenID Connect specifications.
If the Relying Party
is a Smart Contract, response_type=eip712 may be preferred. With this option,
the returned id_token
is an EIP-712 structure.
Authentication Requestβ
When using response_type=eip712, parameter client_id
must be a JSON object encoded in Base64-URL, with the following fields :
{
"name" : "MySmartContract",
"version" : "1",
"chainId" : 31337,
"contract" : "0x5fbdb2315678afecb367f032d93f642f64180aa3",
"account" : "0x7A997970C01813dc3A010C7d01b50e0d17dc79C0",
"scope" : "0xe78222479cb676528ed8a36f9fd71706a3aaea59040e64d97aa910994897fe3e"
}
name
,version
,chainId
andcontract
fields are part of EIP712Domain.account
: EOA address of user that is being authenticated.scope
: hex-encoded uint256 value, used to further differentiate thesub
by associating it with a specificscope
, enhancing privacy and segregation of PPID across different scopes within the same EIP712Domain.
Authentication Responseβ
If user authenticates successfully, the Client is redirected to redirect_uri
. Field id_token
is a JSON object encoded in Base64-URL :
{
"account" : "0x7A997970C01813dc3A010C7d01b50e0d17dc79C0",
"sub" : "0xb60ae5311a0d065e60c2386ef4c81edddc24fa687503e35d297dc1d1ff704cfe",
"scope" : "0xe78222479cb676528ed8a36f9fd71706a3aaea59040e64d97aa910994897fe3e",
"nonce" : "0x1",
"iat" : 1708984962,
"exp" : 1740281565,
"v" : 27
"r" : "0x075bc5c4ca57b9dd18331654cb97d5ad03e6aca6bc68ff5cbdf9ba3c2afdb060",
"s" : "0xcdd7697eb7d4567c134dff02a81bc29395e5c8c0786c27d544126c104d3e39fe",
}
account
: EOA address of user that is being authenticated.sub
: the PPID, derived from EIP712Domain andscope
.scope
: hex-encoded uint256 value, used to further differentiate thesub
by associating it with a specificscope
within the same EIP712Domain.nonce
: value provided in the initial OIDC authorization request.iat
: UTC timestamp (in seconds)exp
: UTC timestamp (in seconds)v
,r
,s
: EIP712 signature
EIP712 signature verificationβ
The signed id_token
can be verified within a smart contract using the public key available in the JWKS.
Here's a simple example of smart contract that requires a valid EIP712 signature to mint an NFT (ERC721) :
In this example, a simple expiration check on iat
is used to mitigate replay attacks. For stricter scenarios, the use of nonce
might be required to prevent replay attacks.
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MySmartContract is ERC721, EIP712, Ownable {
string private constant TOKEN_NAME = "MySmartContract";
string private constant TOKEN_SYM = "MSC";
string private constant TOKEN_VER = "1";
uint256 private constant SCOPE = 1337;
bytes32 private constant PILEUMAUTH_TYPEHASH =
keccak256(
"PileumAuth(address account,uint256 sub,uint256 scope,uint256 nonce,uint128 iat,uint128 exp)"
);
constructor(address initialOwner) payable
ERC721(TOKEN_NAME, TOKEN_SYM)
EIP712(TOKEN_NAME, TOKEN_VER)
Ownable(initialOwner)
{}
function safeMint(
address account,
uint256 sub,
uint256 scope,
uint256 nonce,
uint128 iat,
uint128 exp,
uint8 v,
bytes32 r,
bytes32 s
) public {
require(exp > iat, "Invalid expiration");
require(block.timestamp < iat + 3600, "Message has expired");
require(scope == SCOPE, "Invalid scope");
address signer = ECDSA.recover(
_hashTypedDataV4(
keccak256(
abi.encode(PILEUMAUTH_TYPEHASH, account, sub, scope, nonce, iat, exp)
)
),
v,
r,
s
);
require(signer == owner(), "Invalid signature");
_safeMint(account, sub);
}
}