SignCallback
Signing data involves using the private key and therefore needs to be secure.
There are many different options how data could be signed.
You might have the private key stored in memory and are therefore able to simply sign the data.
This is the easiest option but also comes with higher security risk.
Storing the private key on a separate device or inside a sandboxed application can increase security.
But to enable these security options, we need a generic interface to talk to the signer.
This is what the SignCallback
does.
The SignCallback
defines an interface between the SDK and an arbitrary signing strategy.
May it be a ledger, an air gapped phone or your browser extension.
The interface is generic enough to support implementations for all these security measures.
The SignCallback Family
There are three types of signing callbacks:
- The
SignCallback
is the most general and can be used in almost all cases, except when signing a full DID creation transaction. - The
SignExtrinsicCallback
is a specialSignCallback
which can only be used to sign extrinsics. Thus, everySignCallback
can also be used as aSignExtrinsicCallback
. - The
GetStoreTxSignCallback
can only be used to sign the creation of a new DID.
SignCallback
The plain SignCallback
signs arbitrary data.
It is called with SignRequestData
which contains
- the
data
asUInt8Array
that should be signed - the
keyRelationship
which specifies which DID key must be used - and the
did
(DidUri
) which specifies the DID that must sign the data
The callback is expected to return a SignResponseData
which contains
- the
signature
as anUInt8Array
- the
keyUri
which identifies the key that was used for signing - and the
keyType
which specifies the signature scheme that was used (eithersr25519
,ed25519
orecdsa
)
The signed callback can be used as a closure. If you already have the private key of the DID stored in the surrounding scope, you can just use this key.
- Typescript
- Javascript
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as Kilt from '@kiltprotocol/sdk-js'
export function useSignCallback(
keyUri: Kilt.DidResourceUri,
didSigningKey: Kilt.KeyringPair & { type: 'sr25519' | 'ed25519' }
): Kilt.SignCallback {
const signCallback: Kilt.SignCallback = async ({
data,
// The key relationship specifies which DID key must be used.
keyRelationship,
// The DID URI specifies which DID must be used. We already know which DID
// this will be since we will use this callback just a few lines later (did === didUri).
did
}) => ({
signature: didSigningKey.sign(data),
keyType: didSigningKey.type,
keyUri
})
return signCallback
}
# loading code...
SignExtrinsicCallback
The SignExtrinsicCallback
is a special case of the SignCallback
.
Signing an extrinsic doesn't require the keyUri
as a return value since the chain will pick the appropriate key using information from the extrinsic.
The extrinsic that is submitted has a specific VerificationKeyRelationship
, which defines which key must be used to sign the extrinsic.
Using this relation between extrinsic and key, the chain looks up the public key and verifies the signature.
The SignExtrinsicCallback
is called with the same SignRequestData
, but can return a SignResponseData
that doesn't contain the keyUri
but only
- the
signature
as anUInt8Array
- and the
keyType
which specifies the signature scheme that was used (eithersr25519
,ed25519
orecdsa
).
- Typescript
- Javascript
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as Kilt from '@kiltprotocol/sdk-js'
import { Extrinsic } from '@polkadot/types/interfaces'
export async function useSignExtrinsicCallback(
didUri: Kilt.DidUri,
didSigningKey: Kilt.KeyringPair & { type: 'sr25519' | 'ed25519' },
extrinsic: Extrinsic,
submitterAddress: Kilt.KiltAddress
) {
// The SignExtrinsicCallback is a more specialized SignCallback since it doesn't
// need to return the keyUri.
const signCallback: Kilt.SignExtrinsicCallback = async ({
data,
// The key relationship specifies which DID key must be used.
keyRelationship,
// The DID URI specifies which DID must be used. We already know which DID
// this will be since we will use this callback just a few lines later (did === didUri).
did
}) => ({
signature: didSigningKey.sign(data),
keyType: didSigningKey.type
})
return await Kilt.Did.authorizeTx(
didUri,
extrinsic,
signCallback,
submitterAddress
)
}
# loading code...
GetStoreTxSignCallback
The GetStoreTxSignCallback
is only used to sign the data that is submitted to the blockchain when a DID is being created.
Because there is no DID identifier before the DID is registered on chain, this callback doesn't receive the DID as a parameter.
There is also no DID document and no public key stored if the DID hasn't yet been created.
Therefore the keyUri
cannot point to a valid DID key and is not included in the return data.
- Typescript
- Javascript
import * as Kilt from '@kiltprotocol/sdk-js'
export async function useStoreTxSignCallback(
submitterAddress: Kilt.KiltAddress
): Promise<Kilt.SubmittableExtrinsic> {
// Here we create a new key pair for the DID that will be created later.
// This step might happen in an extension or else where, depending on your application.
const authenticationKey: Kilt.KiltKeyringPair =
Kilt.Utils.Crypto.makeKeypairFromSeed()
// This is the sign callback. We use the just created key to sign arbitrary data
// and return the signature together with the key type.
const getStoreTxSignCallback: Kilt.Did.GetStoreTxSignCallback = async ({
data
}) => ({
signature: authenticationKey.sign(data),
keyType: authenticationKey.type
})
// Here we use the call back
return await Kilt.Did.getStoreTx(
{
authentication: [authenticationKey]
},
submitterAddress,
getStoreTxSignCallback
)
}
# loading code...
Signing using an extension
🚧 This section is work in progress 🚧