decentralized-identity / veramo

A JavaScript Framework for Verifiable Data
https://veramo.io
Apache License 2.0
441 stars 132 forks source link

`EthrDIDProvider networks[] for Linea` config does not work for `didManagerAddService with options: { metaIdentifierKeyId }` #1288

Open hylim-tech-lover opened 1 year ago

hylim-tech-lover commented 1 year ago

Bug severity 3

Describe the bug It came to notice that we will need to use meta-transaction with options: { metaIdentifierKeyId } for didManagerAddService as it is encapsulated as eth_sendTransaction RPC call under the hood.

However, despite setting up the parameters accordingly in correspond to the discussion thread in Discord which will be elaborated in To Reproduce section, the didManagerAddService still failed with the error shown in Observed behaviour section.

The code example is derived from Veramo Node Tutorial Example

To Reproduce Steps to reproduce the behaviour (Linux OS):

  1. Create empty folder with NodeJS installed (version stated below).

  2. Create .env file with following:

    INFURA_API_KEY=
    KMS_SECRET_KEY=  '< you can generate a key by running `npx @veramo/cli config create-secret-key` in a terminal>'
    WALLET_PRIVATE_KEY=
  3. Obtain the following environment variable as followed:

  4. The example of final .env file:

    INFURA_API_KEY=xxxx
    KMS_SECRET_KEY=xxxxx
    WALLET_PRIVATE_KEY=xxxxx
  5. Create package.json and insert the following:

        {
        "name": "veramo-meta-tx-testing",
        "engines": {
            "node": ">=21.0.0 <22.0.0"
        },
        "type": "module",
        "scripts": {
            "execute-with-env": "esrun --node-env-file=.env"
        },
        "dependencies": {
            "@veramo/core": "5.5.3",
            "@veramo/credential-w3c": "5.5.3",
            "@veramo/data-store": "5.5.3",
            "@veramo/did-manager": "5.5.3",
            "@veramo/did-provider-ethr": "5.5.3",
            "@veramo/did-resolver": "5.5.3",
            "@veramo/key-manager": "5.5.3",
            "@veramo/kms-local": "5.5.3"
        },
        "devDependencies": {
            "@digitak/esrun": "3.2.24",
            "sqlite3": "^5.1.6",
            "typescript": "^5.1.6"
        }
    }
  6. Run npm install to install all dependencies

  7. Create tsconfig.json and insert the following:

    {
        "compilerOptions": {
            "preserveConstEnums": true,
            "strict": true,
            "target": "esnext",
            "module": "esnext",
            "rootDir": "./",
            "moduleResolution": "node",
            "esModuleInterop": true,
            "downlevelIteration": true
        }
    }
  8. Create setup.ts and insert the following:

    // Core interfaces
    import {
        createAgent,
        IDIDManager,
        IResolver,
        IDataStore,
        IDataStoreORM,
        IKeyManager,
        ICredentialPlugin,
    } from '@veramo/core';
    
    // Core identity manager plugin
    import { DIDManager } from '@veramo/did-manager';
    
    // Ethr did identity provider
    import { EthrDIDProvider } from '@veramo/did-provider-ethr';
    
    // Core key manager plugin
    import { KeyManager } from '@veramo/key-manager';
    
    // Custom key management system
    import { KeyManagementSystem, SecretBox } from '@veramo/kms-local';
    
    // W3C Verifiable Credential plugin
    import { CredentialPlugin } from '@veramo/credential-w3c';
    
    // Custom resolvers
    import { DIDResolverPlugin } from '@veramo/did-resolver';
    import { Resolver } from 'did-resolver';
    import { getResolver as ethrDidResolver } from 'ethr-did-resolver';
    
    // Storage plugin using TypeOrm
    import {
        DIDStore,
        Entities,
        KeyStore,
        migrations,
        PrivateKeyStore,
    } from '@veramo/data-store';
    
    // TypeORM is installed with '@veramo/data-store'
    import { DataSource } from 'typeorm';
    
    // DB setup:
    const dbConnection = new DataSource({
        type: 'sqlite',
        database: 'database.sqlite',
        synchronize: false,
        migrations,
        migrationsRun: true,
        logging: ['error', 'info', 'warn'],
        entities: Entities,
    }).initialize();
    
    export const agent = createAgent<
        IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver & ICredentialPlugin
    >({
        plugins: [
            new KeyManager({
                store: new KeyStore(dbConnection),
                kms: {
                    local: new KeyManagementSystem(new PrivateKeyStore(dbConnection, new SecretBox(process.env.KMS_SECRET_KEY ?? ''))),
                },
            }),
            new DIDManager({
                store: new DIDStore(dbConnection),
                defaultProvider: 'did:ethr:linea:goerli',
                providers: {
                    'did:ethr': new EthrDIDProvider({
                        defaultKms: 'local',
                        networks: [
                            {
                                name: 'linea:goerli',
                                rpcUrl: `https://linea-goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
                                registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
                            },
                        ],
                    }),
                },
            }),
            new DIDResolverPlugin({
                resolver: new Resolver({
                    ...ethrDidResolver({
                        infuraProjectId: process.env.INFURA_API_KEY,
                        networks: [
                            {
                                name: 'linea:goerli',
                                rpcUrl: `https://linea-goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
                                registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
                            },
                        ],
                    }),
                }),
            }),
            new CredentialPlugin(),
        ],
    })
  9. Create add-service-endpoint.ts and insert the following:

    import { computePublicKey, SigningKey } from '@ethersproject/signing-key';
    import * as u8a from 'uint8arrays';
    import { MinimalImportableKey } from '@veramo/core';
    import { agent } from './setup';
    
    async function main() {
        try {
            // Helper function to find or create did if not found with default alias
            // Added for ease of test and reproduce
            const didToBeUpdated = await findOrCreateNewDid();
    
            if (didToBeUpdated) {
                // Helper function to create DID from private key that holds fund
                const importedIdentifier = await getMetaIdentifier();
                const res = await agent.didManagerAddService({
                    did: didToBeUpdated.did,
                    service: {
                        id: `${didToBeUpdated.did}#Test`,
                        type: 'Test',
                        serviceEndpoint: 'https://test.example.com',
                    },
                    options: { metaIdentifierKeyId: importedIdentifier.controllerKeyId },
                });
                console.log('success update DIDDoc of', didToBeUpdated.did ,"with txHash: " , res);
            }
        } catch (e) {
            console.error(e);
        }
    }
    
    main().catch(console.log);
    
    function importableKeyFromPrivateKey(
        privateKeyHex: string,
        kms: string,
    ): MinimalImportableKey {
        const privateBytes = u8a.fromString(privateKeyHex.toLowerCase(), 'base16');
        const keyPair = new SigningKey(privateBytes);
        const publicKeyHex = keyPair.publicKey.substring(2);
        return {
            type: 'Secp256k1',
            kid: publicKeyHex,
            privateKeyHex,
            publicKeyHex,
            kms,
            meta: {
                algorithms: [
                    'ES256K',
                    'ES256K-R',
                    'eth_signTransaction',
                    'eth_signTypedData',
                    'eth_signMessage',
                    'eth_rawSign',
                ],
            },
        };
    };
    
    async function getMetaIdentifier() {
        const privateKeyHex = process.env.WALLET_PRIVATE_KEY ?? '';
        const key = importableKeyFromPrivateKey(privateKeyHex, 'local');
        const compressedPublicKey = computePublicKey(`0x${key.publicKeyHex}`, true);
        const importedDid = `did:ethr:linea:goerli:${compressedPublicKey}`;
    
        return await agent.didManagerImport({
            did: importedDid,
            provider: 'did:ethr:linea:goerli',
            controllerKeyId: key.kid ?? `test`,
            keys: [key],
            services: [],
        });
    }
    
    async function findOrCreateNewDid() {
        const dids = await agent.didManagerFind({alias: 'linea-goerli-test'});
    
        console.log(`There are ${dids.length} identifiers`);
    
        if (dids.length > 0) {
            if (dids.length == 1) return dids[0];
        }
        else {
            const newDid = await agent.didManagerCreate({alias: 'linea-goerli-test'});
            console.log(`New did created:`);
            console.log(JSON.stringify(newDid, null, 2));
            return newDid;
        }
    }
  10. Should have the similar file structure upon the steps above: image

  11. Run npm run execute-with-env add-service-endpoint.ts at project root directory and fails with error shown in Observed behaviour

Observed behaviour

Error: missing provider (operation="sendTransaction", code=UNSUPPORTED_OPERATION, version=abstract-signer/5.7.0)
    at Logger.Logger.makeError (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/logger/src.ts/index.ts:269:28)
    at Logger.Logger.throwError (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/logger/src.ts/index.ts:281:20)
    at KmsEthereumSigner.Signer._checkProvider (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:330:38)
    at KmsEthereumSigner.<anonymous> (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:123:14)
    at step (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:1:14)
    at Object.next (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:1:14)
    at <anonymous> (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:1:14)
    at new Promise (<anonymous>)
    at __awaiter (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:1:14)
    at KmsEthereumSigner.Signer.sendTransaction (<home-path>/veramo-meta-tx-issue/node_modules/@ethersproject/abstract-signer/src.ts/index.ts:122:70) {
  reason: 'missing provider',
  code: 'UNSUPPORTED_OPERATION',
  operation: 'sendTransaction'
}

Expected behaviour It show success with txHashfrom didManagerAddService

success update DIDDoc of: did:ethr:linea:goerli:0x01g6..8t7p  with txHash: <txHash generated from the transaction sent to provider>

Example of success behavior will be illustrated in Details section with further elaboration

Details

  1. Upon encountered with the Observed behaviour, I have tried to utilize the deprecated attributes in setup.ts:
    ...
    export const agent = createAgent<
        IDIDManager & IKeyManager & IDataStore & IDataStoreORM & IResolver & ICredentialPlugin
    >({
        plugins: [
            ...
            new DIDManager({
                store: new DIDStore(dbConnection),
                defaultProvider: 'did:ethr:linea:goerli',
                providers: {
                    'did:ethr': new EthrDIDProvider({
                        defaultKms: 'local',
    +                    network: 'linea:goerli',
    +                    rpcUrl: `https://linea-goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
    +                    registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
    -                    networks: [
    -                        {
    -                            name: 'linea:goerli',
    -                            rpcUrl: `https://linea-goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
    -                            registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
    -                        },
    -                    ],
                    }),
                },
            }),
            new DIDResolverPlugin({
                resolver: new Resolver({
                    ...ethrDidResolver({
                        infuraProjectId: process.env.INFURA_API_KEY,
                        networks: [
                            {
                                name: 'linea:goerli',
                                rpcUrl: `https://linea-goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
                                registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
                            },
                        ],
                    }),
                }),
            }),
            new CredentialPlugin(),
        ],
    })
  2. Run npm run execute-with-env add-service-endpoint.ts at project root directory and it will success as shown below:
    success update DIDDoc of: did:ethr:linea:goerli:0x01g6..8t7p  with txHash:  0x56548..fe5f
  3. To verify the updated DID, create list-did.ts and insert the following:

    import { agent } from './setup'
    
    async function main() {
    const identifiers = await agent.didManagerFind({alias: 'linea-goerli-test'});
    
    console.log(`There are ${identifiers.length} identifiers`)
    
    if (identifiers.length > 0) {
        identifiers.map((id) => {
        console.log(id)
        console.log('..................')
        })
    }
    }
    
    main().catch(console.log)
  4. Run npm run execute-with-env list-did.ts and we could see that service is added as shown below:
    Identifier {
    did: 'did:ethr:linea:goerli:0x...',
    provider: 'did:ethr:linea:goerli',
    alias: 'linea-goerli-test',
    controllerKeyId: '04...',
    keys: [
      Key {
        kid: '...',
        kms: 'local',
        type: 'Secp256k1',
      }
    ],
    services: [
      Service {
        id: 'did:ethr:linea:goerli:0x0...#Test',
        type: 'Test',
        serviceEndpoint: 'https://test.example.com',
        description: null,
        identifier: undefined
      }
    ],
    ...
    }

Additional context

Versions:

stale[bot] commented 10 months ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

hyLim6519 commented 1 month ago

As additional knowledge for all, we are able to get this work with the following config mentioned in https://github.com/decentralized-identity/veramo/issues/1420:

     networks: [
        {
          name: SEPOLIA_TESTNET_NAMESPACE,
          provider: new JsonRpcProvider(
            SEPOLIA_TESTNET_RPC_URL,
            SEPOLIA_TESTNET_CHAINID,
+           { staticNetwork: Network.from(SEPOLIA_TESTNET_CHAINID) },  // <<<--- The important part.
          ),
          rpcUrl: SEPOLIA_TESTNET_RPC_URL,
          registry: '0x03d5003bf0e79c5f5223588f347eba39afbc3818',
          chainId: SEPOLIA_TESTNET_CHAINID,
        },
      ],

The information above could be a workaround on getting this to work.