Skip to content
Merged
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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,34 @@ If a call requires a DID assertion method key, either `ATT_MNEMONIC` or `DID_MNE

To run this script, execute `yarn call-authorize` and then copy the HEX-encoded operation to be submitted via [PolkadotJS Apps][polkadot-apps] in `Developer > Extrinsics > Decode`, using the account specified in `SUBMITTER_ADDRESS`.

## Generate a DIP signature with a DID key

This script signs any valid HEX-encoded call of any other parachain with the right key re-generated from the provided seedling information, i.e., either with the provided mnemonic, or with the provided combination of base mnemonic and derivation path.

Valid HEX-encoded calls can be generated by interacting with [PolkadotJS Apps][polkadot-apps] under the `Developer > Extrinsics` menu.
Once the right call (i.e., the right pallet and right method) with the right parameters has been specified, the HEX-encoded value under `encoded call data` can be copied and passed as parameter to this script.

The following env variables are required:

- `CONSUMER_WS_ADDRESS`: The endpoint address of the consumer chain on which DIP is to be used.
- `SUBMITTER_ADDRESS`: The address (encoded with the target chain network prefix `38`) that is authorized to submit the transaction on the target chain.
- `ENCODED_CALL`: The HEX-encoded call to DID-sign.
- `DID_URI`: The URI of the DID authorizing the operation
- `VERIFICATION_METHOD`: The verification method of the DID key to use. Because this script is not able to automatically derive the DID key required to sign the call on the target chain, it has to be explicitely set with this variable. Example values are `authentication`, `assertionMethod`, and `capabilityDelegation`.

The following optional env variables can be passed:

- `IDENTITY_DETAILS`: The runtime type definition of the identity details stored on the consumer chain, according to the DIP protocol. It defaults to `u128`, which represents a simple nonce value.
- `ACCOUNT_ID`: The runtime type definition of account address on the consumer chain. It defaults to `AccountId32`, which is the default of most Substrate-based chains. Some chains might use `AccountId20`.

As with DID creation, there is no strong requirement on what other variables must be set.
Depending on the expected key to be used to sign the call, the right mnemonic or the right base mnemonic + derivation path must be provided.

For instance, if a call requires a DID authentication key, either `AUTH_MNEMONIC` or `DID_MNEMONIC` and `AUTH_DERIVATION_PATH` must be specified.
If a call requires a DID assertion method key, either `ATT_MNEMONIC` or `DID_MNEMONIC` and `ATT_DERIVATION_PATH` must be specified.

To run this script, execute `yarn dip-sign` and then copy the generated signature and block number to be submitted via [PolkadotJS Apps][polkadot-apps] as part of the DIP tx submission process, using the account specified in `SUBMITTER_ADDRESS`.

## Change a DID key

The following env variables are required:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"att-key-set": "ts-node src/att-key-set.ts",
"del-key-set": "ts-node src/del-key-set.ts",
"did-create": "ts-node src/did-create.ts",
"dip-sign": "ts-node src/dip-sign.ts",
"call-authorize": "ts-node src/call-sign",
"check-ts": "tsc src/** --skipLibCheck --noEmit",
"lint": "eslint . --ext .ts --format=codeframe",
Expand Down
78 changes: 78 additions & 0 deletions src/dip-sign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dotenv/config'

import * as Kilt from '@kiltprotocol/sdk-js'
import { ApiPromise, WsProvider } from '@polkadot/api'

import * as utils from './utils'

async function main() {
const consumerWsAddress = process.env[utils.envNames.consumerWsAddress]
if (consumerWsAddress === undefined) {
throw new Error(
`No ${utils.envNames.consumerWsAddress} env variable specified.`
)
}
const api = await ApiPromise.create({ provider: new WsProvider(consumerWsAddress) })

const submitterAddress = process.env[
utils.envNames.submitterAddress
] as Kilt.KiltAddress
if (submitterAddress === undefined) {
throw new Error(
`No "${utils.envNames.submitterAddress}" env variable specified.`
)
}

// eslint-disable-next-line max-len
const authKey =
utils.generateAuthenticationKey() ??
Kilt.Utils.Crypto.makeKeypairFromUri('//Alice')
const assertionKey = utils.generateAttestationKey()
const delegationKey = utils.generateDelegationKey()

const didUri = process.env[utils.envNames.didUri] as Kilt.DidUri
if (didUri === undefined) {
throw new Error(`"${utils.envNames.didUri}" not specified.`)
}

const encodedCall = process.env[utils.envNames.encodedCall]
const decodedCall = api.createType('Call', encodedCall)

const [requiredKey, verificationMethod] = (() => {
const providedMethod = utils.parseVerificationMethod()
switch (providedMethod) {
case 'authentication':
return [authKey, providedMethod]
case 'assertionMethod':
return [assertionKey, providedMethod]
case 'capabilityDelegation':
return [delegationKey, providedMethod]
}
})()
if (requiredKey === undefined) {
throw new Error(
'The DID key to authorize the operation is not part of the DID Document. Please add such a key before re-trying.'
)
}
const [dipSignature, blockNumber] =
await utils.generateDipTxSignature(
api,
didUri,
decodedCall,
submitterAddress,
verificationMethod,
utils.getKeypairTxSigningCallback(requiredKey),
)

console.log(
`
DID signature for submission via DIP: ${JSON.stringify(utils.hexifyDipSignature(dipSignature), null, 2)}.
Block number used for signature generation: ${blockNumber.toString()}.
Please add these details to the "dipConsumer.dispatchAs" function in PolkadotJS.
`
)
}

main()
.catch((e) => console.error(e))
.then(() => process.exit(0))
83 changes: 78 additions & 5 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Keyring } from '@polkadot/api'
import type { BN } from '@polkadot/util'
import type { Call } from '@polkadot/types/interfaces'

import { ApiPromise, Keyring } from '@polkadot/api'
import { KeyringPair } from '@polkadot/keyring/types'
import { u8aToHex } from '@polkadot/util'

import * as Kilt from '@kiltprotocol/sdk-js'

Expand All @@ -20,20 +25,28 @@ export const envNames = {
delDerivationPath: 'DEL_DERIVATION_PATH',
delKeyType: 'DEL_KEY_TYPE',
encodedCall: 'ENCODED_CALL',
consumerWsAddress: 'CONSUMER_WS_ADDRESS',
verificationMethod: 'VERIFICATION_METHOD',
identityDetailsType: 'IDENTITY_DETAILS',
accountIdType: 'ACCOUNT_ID'
}

type Defaults = {
wsAddress: string
authKeyType: Kilt.KeyringPair['type']
attKeyType: Kilt.KeyringPair['type']
delKeyType: Kilt.KeyringPair['type']
identityDetailsType: string,
accountIdType: string
}

export const defaults: Defaults = {
wsAddress: 'wss://spiritnet.kilt.io',
authKeyType: 'sr25519',
attKeyType: 'sr25519',
delKeyType: 'sr25519',
identityDetailsType: 'u128',
accountIdType: 'AccountId32'
}

export function getKeypairSigningCallback(
Expand Down Expand Up @@ -90,7 +103,7 @@ export function generateAuthenticationKey(): Kilt.KiltKeyringPair | undefined {
authKeyMnemonic === undefined
? undefined
: (process.env[envNames.authKeyType] as Kilt.KeyringPair['type']) ||
defaults.authKeyType
defaults.authKeyType
if (authKeyMnemonic !== undefined) {
return new Keyring().addFromMnemonic(
authKeyMnemonic,
Expand Down Expand Up @@ -125,7 +138,7 @@ export function generateAttestationKey(): Kilt.KiltKeyringPair | undefined {
attKeyMnemonic === undefined
? undefined
: (process.env[envNames.attKeyType] as Kilt.KeyringPair['type']) ||
defaults.attKeyType
defaults.attKeyType
if (attKeyMnemonic !== undefined) {
return new Keyring().addFromMnemonic(
attKeyMnemonic,
Expand Down Expand Up @@ -160,7 +173,7 @@ export function generateDelegationKey(): Kilt.KiltKeyringPair | undefined {
delKeyMnemonic === undefined
? undefined
: (process.env[envNames.delKeyType] as Kilt.KeyringPair['type']) ||
defaults.delKeyType
defaults.delKeyType
if (delKeyMnemonic !== undefined) {
return new Keyring().addFromMnemonic(
delKeyMnemonic,
Expand Down Expand Up @@ -197,7 +210,7 @@ export function generateNewAuthenticationKey():
authKeyMnemonic === undefined
? undefined
: (process.env[envNames.newAuthKeyType] as Kilt.KeyringPair['type']) ||
defaults.authKeyType
defaults.authKeyType
if (authKeyMnemonic !== undefined) {
return new Keyring().addFromMnemonic(
authKeyMnemonic,
Expand All @@ -208,3 +221,63 @@ export function generateNewAuthenticationKey():
return undefined
}
}

const validValues: Set<Kilt.VerificationKeyRelationship> = new Set(['authentication', 'assertionMethod', 'capabilityDelegation'])
export function parseVerificationMethod(): Kilt.VerificationKeyRelationship {
const verificationMethod = process.env[envNames.verificationMethod]
if (verificationMethod === undefined) {
throw new Error(`No ${envNames.verificationMethod} env variable specified.`)
}
const castedVerificationMethod = verificationMethod as Kilt.VerificationKeyRelationship
if (validValues.has(castedVerificationMethod)) {
return castedVerificationMethod
} else {
throw new Error(`Provided value for ${envNames.verificationMethod} does not match any of the expected values: ${validValues}.`)
}
}

export async function generateDipTxSignature(
api: ApiPromise,
did: Kilt.DidUri,
call: Call,
submitterAccount: KeyringPair['address'],
didKeyRelationship: Kilt.VerificationKeyRelationship,
sign: Kilt.SignExtrinsicCallback,
): Promise<[Kilt.Did.EncodedSignature, BN]> {
const isDipCapable = api.tx.dipConsumer.dispatchAs !== undefined
if (!isDipCapable) {
throw new Error(`The target chain at does not seem to support DIP.`)
}
const blockNumber = await api.query.system.number()
console.log(`DIP signature targeting block number: ${blockNumber.toHuman()}`)
const genesisHash = await api.query.system.blockHash(0)
console.log(`DIP consumer genesis hash: ${genesisHash.toHuman()}`)
const identityDetails = await api.query.dipConsumer.identityProofs(Kilt.Did.toChain(did))
const identityDetailsType = process.env[envNames.identityDetailsType] ?? defaults.identityDetailsType
console.log(
`DIP subject identity details on consumer chain: ${JSON.stringify(identityDetails, null, 2)} with runtime type "${identityDetailsType}"`
)
const accountIdType = process.env[envNames.accountIdType] ?? defaults.accountIdType
console.log(
`DIP AccountId runtime type: "${accountIdType}"`
)
const signaturePayload =
api.createType(
`(Call, ${identityDetailsType}, ${accountIdType}, BlockNumber, Hash)`,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[call, (identityDetails as any).details, submitterAccount, blockNumber, genesisHash]
).toU8a()
console.log(`Encoded payload for signing: ${u8aToHex(signaturePayload)}`)
const signature = await sign({ data: signaturePayload, keyRelationship: didKeyRelationship, did })
return [{
[signature.keyType]: signature.signature
} as Kilt.Did.EncodedSignature, blockNumber.toBn()]
}

export function hexifyDipSignature(signature: Kilt.Did.EncodedSignature) {
const [signatureType, byteSignature] = Object.entries(signature)[0]
const hexifiedSignature = {
[signatureType]: u8aToHex(byteSignature)
}
return hexifiedSignature
}