@sphereon/ssi-sdk.ebsi-support
Version:
460 lines (421 loc) • 14.2 kB
text/typescript
import { W3CVerifiableCredential } from '@sphereon/ssi-types'
import { IAgentContext, IIdentifier, IKeyManager, MinimalImportableKey, TKeyType } from '@veramo/core'
import { IService } from '@veramo/core'
import { DIDDocument } from 'did-resolver'
import { AccessListish, BigNumberish, BytesLike } from 'ethers'
import { ApiOpts, EbsiEnvironment } from '../types/IEbsiSupport'
import { CredentialRole } from '@sphereon/ssi-types'
export type IContext = IAgentContext<IKeyManager>
/**
* The type of the DID to be created
* @readonly
* @enum {string}
*/
export type EbsiDIDType = 'NATURAL_PERSON' | 'LEGAL_ENTITY'
/**
* The DID method to use
* @readonly
* @enum {string}
*/
export type EbsiDIDPrefix = 'did:ebsi:' | 'did:key:'
/**
* @typedef EbsiDidSpecInfo
* @type {object}
* @property {EbsiDIDType} type - The type of the DID
* @property {EbsiDIDPrefix} method - The method of the DID
* @property {number} version - The version of the specs
* @property {number} didLength - The length of the DID
* @property {number} privateKeyLength The private key length
*/
export interface EbsiDidSpecInfo {
type: EbsiDIDType
method: EbsiDIDPrefix
version?: number
didLength?: number
privateKeyLength?: number
}
export const EBSI_DID_SPEC_INFOS: Record<string, EbsiDidSpecInfo> = {
V1: {
type: 'LEGAL_ENTITY',
method: 'did:ebsi:',
version: 0x01,
didLength: 16,
privateKeyLength: 32,
},
KEY: {
type: 'NATURAL_PERSON',
method: 'did:key:',
},
}
/**
* A minimal importable key with restricted types to choose from and purposes of the public key
* @typedef IKeyOpts
* @extends MinimalImportableKey
* @property {EbsiKeyType} type
* @property {EbsiPublicKeyPurpose[]} purposes
*/
export interface IKeyOpts extends WithRequiredProperty<Partial<MinimalImportableKey>, 'privateKeyHex'> {
type?: EbsiKeyType
purposes?: EbsiPublicKeyPurpose[]
}
// Needed to make a single property required
type WithRequiredProperty<Type, Key extends keyof Type> = Type & {
[Property in Key]-?: Type[Property]
}
export type RpcMethodArgs = {
params: RPCParams[]
rpcId: number
accessToken: string
rpcMethod: EbsiRpcMethod
apiOpts?: ApiOpts
doNotThrowErrors?: boolean
}
export type EbsiCreateIdentifierOpts = {
methodSpecificId?: string
rpcId?: number
secp256k1Key?: IKeyOpts
secp256r1Key?: IKeyOpts
did?: string
keys?: IKeyOpts[] // additional importable keys, but only in case execute ledger is true
executeLedgerOperation?: boolean // Whether to persist on the EBSI ledger. By default looks at whether access token opts are set or not
baseDocument?: string
notBefore?: number
notAfter?: number
accessTokenOpts: EbsiAccessTokenOpts
services?: IService[]
}
/**
* @typedef ICreateIdentifierArgs
* @type {object}
* @property {string} kms - The kms to use
* @property {string} alias - The alias of the DID
* @property {EbsiDidSpecInfo} type
* @property {string} options.methodSpecificId - method specific id for import
* @property {IKeyOpts} secp256k1Key - The options to create the key
* @property {IKeyOpts} secp256r1Key - The options to create the key
*/
export interface ICreateIdentifierArgs {
kms?: string
alias?: string
type?: EbsiDidSpecInfo
options: EbsiCreateIdentifierOpts
}
/**
* The Ebsi allowed key types - Secp256k1 and Secp256r1
* @readonly
* @enum {string}
*/
export type EbsiKeyType = Extract<TKeyType, 'Secp256k1' | 'Secp256r1'>
/**
* The purpose of the public keys
* @readonly
* @enum {string}
*/
export enum EbsiPublicKeyPurpose {
Authentication = 'authentication',
AssertionMethod = 'assertionMethod',
CapabilityInvocation = 'capabilityInvocation',
}
/**
* @typedef InsertDidDocumentParams
* @type {object}
* @property {string} from - Ethereum address of the signer
* @property {string} did - DID to insert. It must be for a legal entity (DID v1)
* @property {string} BASE_CONTEXT_DOC - JSON string containing the @context of the DID document
* @property {string} vMethodId - Thumbprint of the public key
* @property {string} publicKey - Public key for secp256k1 in uncompressed format prefixed with "0x04"
* @property {boolean} isSecp256k1 - It must be true
* @property {number} notBefore - Capability invocation is valid from this time
* @property {number} notAfter - Expiration of the capability invocation
*/
export type InsertDidDocumentParams = {
from: string
did: string
baseDocument: string
vMethodId: string
publicKey: string
isSecp256k1: boolean
notBefore: number
notAfter: number
}
/**
* @typedef UpdateBaseDocumentParams
* @type {object}
* @property {string} from - Ethereum address of the signer
* @property {string} did - Existing DID
* @property {string} BASE_CONTEXT_DOC - JSON string containing the @context of the DID document
*/
export type UpdateBaseDocumentParams = Pick<InsertDidDocumentParams, 'from' | 'did' | 'baseDocument'>
/**
* @typedef UpdateIdentifierParams
* @type {object}
* @property {string} did - A DID
* @property {Partial<DIDDocument>} document - The partial DID document
* @property {{ [p: string]: any }} [options] - Any additional options
*/
export type UpdateIdentifierParams = {
did: string
document: Partial<DIDDocument>
options?: { [p: string]: any }
}
export type AddServiceParams = {
from: string
did: string
service: IService
}
/**
* @typedef AddVerificationMethodParams
* @type {object}
* @property {string} from - Ethereum address of the signer
* @property {string} did - Existing DID
* @property {string} vMethodId - New verification method id
* @property {boolean} isSecp256k1 - Boolean defining if the public key is for secp256k1 curve or not
* @property {string} publicKey - Public key as hex string. For an ES256K key, it must be in uncompressed format
* prefixed with "0x04". For other algorithms, it must be the JWK transformed to string and then to hex format.
*/
export type AddVerificationMethodParams = Pick<InsertDidDocumentParams, 'from' | 'did' | 'vMethodId' | 'isSecp256k1' | 'publicKey'>
/**
* @typedef AddVerificationMethodRelationshipParams
* @type {object}
* @property {string} from - Ethereum address of the signer
* @property {string} did - Existing DID
* @property {string} name - Name of the verification relationship
* @property {string} vMethodId - Reference to the verification method
* @property {number} notBefore - Verification relationship is valid from this time
* @property {number} notAfter - Expiration of the verification relationship
*/
export type AddVerificationMethodRelationshipParams = Pick<InsertDidDocumentParams, 'from' | 'did' | 'vMethodId' | 'notBefore' | 'notAfter'> & {
name: string
}
/**
* @typedef UnsignedTransaction
* @type {object}
* @property {string} from - The sending address.
* @property {string} to - The receiving address (if EOA, the transaction will transfer value. If a smart contract
* account, the transaction will use contract code).
* @property {string} data - Can contain code or a message to the recipient.
* @property {string} nonce - A number used to track ordering of transactions and prevent replay attacks
* @property {string} chainId - The Ethereum Network ID (ex: 1 - Ethereum Mainnet).
* @property {string} gasLimit - The maximum amount of gas units that can be used.
* @property {string} gasPrice - Gas price provided by the sender in Wei.
* @property {string} value - The amount of ETH to be sent from the sending address (denominated in Wei)
*/
export type UnsignedTransaction = {
to?: string
nonce?: number
gasLimit?: BigNumberish
gasPrice?: BigNumberish
data?: BytesLike
value?: BigNumberish
chainId?: number
// Typed-Transaction features
type?: number | null
// EIP-2930; Type 1 & EIP-1559; Type 2
accessList?: AccessListish
// EIP-1559; Type 2
maxPriorityFeePerGas?: BigNumberish
maxFeePerGas?: BigNumberish
/*from: string
to: string
data: string
nonce: string
chainId: string
gasLimit: string
gasPrice: string
value: string*/
}
/**
* @typedef SendSignedTransactionParams
* @type {object}
* @property {string} protocol - Example: eth
* @property {UnsignedTransaction} unsignedTransaction - The unsigned transaction
* @property {string} r - ECDSA signature r
* @property {string} s - ECDSA signature s
* @property {string} v - ECDSA recovery id
* @property {string} signedRawTransaction - The signed raw transaction
*/
export type SendSignedTransactionParams = {
protocol: string
unsignedTransaction: UnsignedTransaction
r: string
s: string
v: string
signedRawTransaction: string
}
/**
* @typedef RpcOkResponse
* @type {object}
* @property {string} JSON_RPC_VERSION - Must be exactly "2.0"
* @property {number} id - Same identifier established by the client in the call
* @property {object} result - Result of the transaction
*/
export type RpcOkResponse = {
jsonrpc: string
id: number
result: any
}
export type RpcErrorResponse = {
jsonrpc: string
id: number
error: {
code: number
message: string
}
}
/**
* @typedef ResponseNot200
* @type {object}
* @property {URL | string} type - An absolute URI that identifies the problem type. When dereferenced,
* it SHOULD provide human-readable documentation for the problem type.
* @property {string} title - A short summary of the problem type.
* @property {number} status - The HTTP status code generated by the origin server for this occurrence of the problem.
* @property {string} detail - A human-readable explanation specific to this occurrence of the problem.
* @property {URL | string} instance An absolute URI that identifies the specific occurrence of the problem.
* It may or may not yield further information if dereferenced.
*/
export type ResponseNot200 = {
type: URL | string
id?: number
title: string
error?: {
code: number
message: string
}
status: number
detail: string
instance: URL | string
}
/**
* @typedef GetDidDocumentParams
* @type {object}
* @property {string} did
* @property {string} validAt
*/
export type GetDidDocumentParams = {
did: string
validAt?: string
}
/**
* @typedef GetDidDocumentsParams
* @type {object}
* @property {string} offset Originally page[after] Cursor that points to the end of the page of data that has been returned.
* @property {number} size Originally page[size] Defines the maximum number of objects that may be returned.
* @property {string} controller Filter by controller DID.
*/
export type GetDidDocumentsParams = {
offset?: string
size?: number
controller?: string
}
/**
* Result of listing dids
* @typedef {Item}
* @type {object}
* @property {string} did - The DID
* @property {string} href - The referrer of the DID
*/
export type Item = {
did: string
href: string
}
/**
* The links related to pagination
* @typedef Links
* @type {object}
* @property {string} first - The link to the first page
* @property {string} prev - The link ot the previous page
* @property {string} next - The link to the next page
* @property {string} last - The link to the last page
*/
export type Links = {
first: string
prev: string
next: string
last: string
}
/**
* @typedef GetDidDocumentResponse
* @type {object}
* @property {string} self - Absolute path to the collection (consult)
* @property {Item[]} items - List of DIDs and their referrers
* @property {number} total - Total number of items across all pages.
* @property {pageSize} number - Maximum number of items per page. For the last page, its value should be independent of the number of actually returned items.
* @property {Links} links - The links related to pagination
*/
export type GetDidDocumentsResponse = {
self: string
items: Item[]
total: number
pageSize: number
links: Links
}
export type EbsiAccessTokenOpts = {
attestationToOnboard?: W3CVerifiableCredential
attestationToOnboardCredentialRole: CredentialRole
jwksUri?: string
redirectUri: string
credentialIssuer: string
clientId: string
environment: EbsiEnvironment
}
/**
* @typedef CreateEbsiDidParams
* @type {object}
* @property {Omit<IIdentifier, 'provider'>} identifier An identifier without the provider
* @property {ManagedKeyInfo} secp256k1ManagedKeyInfo A Secp256k1 managed key
* @property {ManagedKeyInfo} secp256r1ManagedKeyInfo A Secp256r1 managed key
* @property {number} id A client created id
* @property {string} from The wallet eth like address
* @property {string} [baseDocument] The base DID document
* @property {number} notBefore Date of issuance of the identifier
* @property {number} notAfter Date of expiration of the identifier
* @property {ApiOpts} [apiOpts] The EBSI API options
*/
export type CreateEbsiDidParams = {
identifier: IIdentifier
rpcId?: number
notBefore?: number
notAfter?: number
baseDocument?: string
accessTokenOpts: EbsiAccessTokenOpts
}
export interface CreateEbsiDidOnLedgerResult {
identifier: IIdentifier
addVerificationMethod: EbsiRPCResponse
insertDidDoc: EbsiRPCResponse
addAssertionMethodRelationship: EbsiRPCResponse
addAuthenticationRelationship: EbsiRPCResponse
}
/**
* @constant JSON_RPC_VERSION
*/
export const JSON_RPC_VERSION = '2.0'
/**
* @constant BASE_CONTEXT_DOC
*/
export const BASE_CONTEXT_DOC = JSON.stringify({ '@context': ['https://www.w3.org/ns/did/v1', 'https://w3id.org/security/suites/jws-2020/v1'] })
export interface EbsiDidRegistryAPIEndpoints {
mutate: string
query: string
}
/**
* The EBSI RPC operations
* @readonly
* @enum {string}
*/
export enum EbsiRpcMethod {
INSERT_DID_DOCUMENT = 'insertDidDocument',
UPDATE_DID_DOCUMENT = 'updateBaseDocument',
ADD_VERIFICATION_METHOD = 'addVerificationMethod',
ADD_VERIFICATION_RELATIONSHIP = 'addVerificationRelationship',
ADD_SERVICE = 'addService',
SEND_SIGNED_TRANSACTION = 'sendSignedTransaction',
}
export type RPCParams =
| InsertDidDocumentParams
| UpdateBaseDocumentParams
| AddVerificationMethodParams
| AddVerificationMethodRelationshipParams
| SendSignedTransactionParams
| AddServiceParams
export type EbsiRPCResponse = RpcOkResponse | (RpcErrorResponse & { nonce: string })