This is a first attempt to write-up in detail following Friday’s (15 jan 2021) discussion with @dholms a plausible, generic UCAN flow for cosigning.
Introducing the actors
We consider a complete situation, explicitly separating the auth-lobby, the backup app (functioning as a wallet application for Filecoin), and a third party app wanting to access the Filecoin wallet to transfer value (and store/retrieve data). A last forth party is a cosigner service. (We can omit for this discussion Powergate and Filecoin as they both operate on their own credential system, and the cosigner functions as the interface.)
- AuthLobby:
did:k:zLobby
(assume equalsuserOriginDid
, without loss of generality) - BackupApp:
did:k:zWallet
, (did for BackupApp/Wallet instance) - 3rdPartyApp:
did:k:zActiveApp
(did for external app which should be constraint in usage of keys) - cosigner:
did:k:zCosigner
(did of a chosen cosigner instance)
notes:
- for the current scope we do not have to consider “third party apps” accessing the wallet. It is used here in a logical discussion to more clearly see and understand the logical separation of interfaces. For what concerns the backup app project, the “third party app” simply is equated with the backup/wallet app.
- we set as an objective that the auth-lobby is and will remain agnostic of any specific subprotocols (cosigning, or more specifically Filecoin)
UCAN attenuation flow
Auth Lobby: Append to WNFS
ucanX =
{
"iss": did:k:zLobby,
"aud": did:k:zWallet,
"prf": [], # origin UCAN
"fct": [], # no facts needed
"att": [
{
"wnfs": "alice.fission.name/Apps/Fission/Backup/",
"cap": "APPEND"
},
{
"wnfs": "alice.fission.name/Keychain/Fission/Backup/",
"cap": "APPEND"
},
{
"keychain": "alice.fission.name/Keychain/Fission/Backup/",
"cap": "ADMIN"
}
]
}
By using a path under keychain
, the auth lobby does not need to explicitly parse public keys in the keychain.
Wallet: Administer keys
With ADMIN
capacity for keychain
resource, the wallet app (with did:k:zWallet
) can request key pair generation from a cosigner;
accompagnied is the wnfs
resource for the same keychain path, to allow the wallet app to write private keys to the filesystem under “/Keychain/Fission/Backup/”.
In initial work, we can assume in verification code for the cosigner that the attentuation
{
"wnfs": "alice.fission.name/Keychain/Fission/Backup/",
"cap": "APPEND"
},
implies also the valid presence of
{
"keychain": "alice.fission.name/Keychain/Fission/Backup/",
"cap": "ADMIN"
}
so that auth lobby code does not need updating until testing and proposals are on solid ground.
To generate a key pair the wallet can create a UCAN for cosigner, omitting the wnfs resource.
ucanA =
{
"iss": did:k:zWallet,
"aud": did:k:zCosigner,
"prf": [ucanX],
"fct": [
{
"challenge": cidUcanX,
"signature": <signature by UserPublicKey of challenge>
}
],
"att": [
{
"keychain": "did:key:[bls-prefix]UserPublicKey",
"cap": "ADMIN"
}
]
}
Under no event is the cosigner sent the WNFS AES keys to access the filesystem, making reading from (and definitely writing to) it impossible. The admin capacity merely authorizes the cosigner generate a bls key on their part, and pair the two keys; the paired key is best referenced by the user public key of the pair.
It is possible to include in the facts
a signature by the UserPublicKey
of a relevant statement (eg. cid of ucanX) - this would prove ownership of the private key to the cosigner. However, it currently isn’t mission critical.
Wallet: creating a session for the active app
The active application using the wallet, can either be the wallet (backup app) itself; or can be an external third party app, resulting in a different audience
did. Therefore did:k:zActiveApp
may equal the did:k:zWallet
and the wallet can issue a session for its own use.
It is required to have ADMIN
capability to attentuate a SESSION
capability; ie. a session can not be used to create another session.
ucanY =
{
"iss": did:k:zWallet,
"aud": did:k:zActiveApp,
"nbt": now-10seconds,
"exp": now+30minutes,
"prf": [ucanX],
"fct": [
{
"challenge": cidUcanX,
"signature": <signature by UserPublicKey of challenge>
}
],
"att": [
{
"keychain": "did:key:[bls-prefix]UserPublicKey",
"cap": "SESSION"
}
]
}
Active app: cosigning messages
The active app is given ucanY
and passed the private key associated with UserPublicKey
, similar to the AES key that is shared from the lobby, encrypted with did:k:zActiveApp
and passed back to the Active App. The Active app does not need WNFS access to the keychain directory.
It would be good practice to write in WNFS a copy of messages the app sends out, and other metadata; however, this may be better kept in the app directory rather than the keychain directory.
For a message to be sent, the active app can formulate it and sign it. Following it can generate following UCAN to request cosignature and delivery to the network.
ucanZ =
{
"iss": did:k:zActiveApp,
"aud": did:k:zCosigner,
"prf": [ucanY(ucanX)],
"fct": [
{
"challenge": messageHash,
"signature": <signature by UserPublicKey of messageHash>
}
],
"att": [
{
"keychain": "did:key:[bls-prefix]UserPublicKey",
"cap": "COSIGN"
}
]
}
Keychain resource and capabilities
capabilities in increasing order:
- COSIGN (issued to cosigner)
can sign within limits of parent session ucan - SESSION (issued to the active app)
- ADMIN (issued to wallet)
Open questions
- if the session should be restricted by more than a time window, eg. a max amount of tokens to send, or fiat value to send (approximately), or be restricted in the transaction calls allowed, the question is how this should be specified in the attentuation.
- it may be important to enforce a linear sequence; this can be achieved by “sequencing” the cosigning UCANs and requiring as
proof
either the previous cosign ucan, or initiate the chain with the session ucan.