Can not verify signature for commit...not a valid verificationMethod for issuer

Hello!

I am trying to create a backend using ceramic, composedb and graphql. I have been able to write and read data, but updating data is where I am having some trouble. The issue that arises is when I am trying to update data that was created in previous instances. If I created the piece of data within one instance and am then able to update that piece of data, but nothing before.

I believe I need something like an admin DID that can update any information created, but I would also like to have it where the user can update their own information. For example, if userA and userB create an account they should be able to update and change their own account, but not each others.

Here is how I am authenticating the session:

export function generateSeed(length = 32) {
  const seedHex = CryptoJS.lib.WordArray.random(length).toString();
  // Convert hex to Uint8Array
  const seed = new Uint8Array(
    seedHex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
  );
  return seed;
}

export async function authenticateCeramic(seed) {
  const ceramic = new CeramicClient("http://localhost:7007");

  const provider = new Ed25519Provider(seed);
  const did = new DID({ provider, resolver: getResolver() });
  await did.authenticate();

  ceramic.did = did;
  compose = new ComposeClient({
    ceramic: ceramic,
    definition: Composite,
  });

  compose.setDID(ceramic.did);
  return ceramic;
}

This is how I use it in my App.js file :

  const [ceramicInstance, setCeramicInstance] = useState(null);

  useEffect(() => {
    const seed = generateSeed();
    authenticateCeramic(seed).then(setCeramicInstance);
  }, []);

This is the error:

Can not verify signature for commit bagcqceraspq6pldhh7n3rn2b4ccfhzxf7peoa5dd3i2ovzwfbbxi4ddzg3ea to stream kjzl6kcym7w8y6y0ndww9lvua2qvrtw18p2gb47tavzdysnult7mzga6dr6tif0 which has controller DID did:key:z6Mkjf8mwFDJUVCJvgiXpAoU6k61y61WN3UUGQV2svz6Vegc: invalid_jws: not a valid verificationMethod for issuer: did:key:*admin-did*
1 Like

Hi @mpatel31, sorry for the delay, looks like this question slipped through the cracks. Were you able to get this working?

Actually I think I see the issue. It looks like every time the user authenticates they get a new randomly-generated seed, which will authenticate them with a new DID. For a user to be able to update their own data they need to be signing in with a consistent DID. So they will need a consistent seed. You’ll need to figure out a secure way to store the seed - key management is definitely one of the trickier parts of trying to use a backend to write to Ceramic. If the app is a web app, you can check out our didSession library which lets the user authenticate using a browser-based crypto wallet like Metamask.

Hello Spencer,

I am still running into this problem. I was trying to go about this without using a didSession, do you know if there is another way of getting around this?

I’ve tried to use a global DID, but the main thing that I was wondering, if I go with this route, would I be able to add admins that can edit certain information. The goal of the app is to have admins as a part of an account and that will allow them to change information related to said account.

So one core idea of Ceramic is that whoever publishes the data to Ceramic controls it. Every Stream created on Ceramic has a controller DID, and only that DID (or another DID that the controller DID grants access) is allowed to update that Stream. So there’s no real way to have a global admin user that is allowed to modify other user’s data without that user’s permission. You could have a global DID that controls all streams and then issues object capabilities to other users to write data on behalf of the global user, but any delegation of write permission requires the original data owner to explicitly authorize the additional user. Does that make sense?

This does make sense to me! I guess I am just a little confused as to how that translates into code. I have tried to “useContext” just like the example project that is show in the starter video for ceramic. However, even with that, I have had the same error show up.

The page refreshes and forgets the DID that created that piece of data. Would I need to have a session (Ethereum or something like this), in order for me to achieve what I want to happen?

You need to ensure that the seed being given to the Ed25519Provider is consistent every time the authentication code runs. How you store the seed is up to you, there are many options with different tradeoffs around security and user experience. Can you please clarify if this is a web app or if you are trying to write to Ceramic from a backend? The options available for secure seed storage are going to be different depending on if you’re in a browser-based context or on a server.

So sorry for the late reply, I didn’t get a notification about this for some reason.

To clarify it will be a server, but the information stored will be queried to the mobile app.

Update on where I am with this. I am receiving the same error, but I believe I have made the code for generating and store seeds and DIDs better.

I am generating the seed and storing it within local storage of the web page. The code below shows how I am retrieving and authenticating the did.

export async function resolveSeedToDID(seed) {
  const provider = new Ed25519Provider(seed);
  const did = new DID({
    provider,
    resolver: getResolver(),
  });
  await did.authenticate();
  return did.id; // This is the DID string
}

export async function authenticateCeramic(ceramic, seed) {
  const provider = new Ed25519Provider(seed);
  const did = new DID({ provider, resolver: getResolver() });
  await did.authenticate();

  ceramic.did = did;

  const compose = new ComposeClient({
    ceramic: ceramic,
    definition: Composite,
  });

  compose.setDID(ceramic.did);

  return { ceramic, compose };
}

I am using Context with my application. Which is the provided code below:

const CeramicContext = createContext();

export const CeramicWrapper = ({ children }) => {
  const [ceramic, setCeramic] = useState(null);
  const [composeClient, setComposeClient] = useState(null);
  const [userName, setUserName] = useState(
    sessionStorage.getItem("userName") || ""
  );

  useEffect(() => {
    if (userName) {
      sessionStorage.setItem("userName", userName);
      const initializeCeramic = async () => {
        const ceramicInstance = new CeramicClient("http://localhost:7007");
        const seed = generateSeed(userName);
        const did = await resolveSeedToDID(seed);
        const { ceramic: authenticatedCeramic } = await authenticateCeramic(
          ceramicInstance,
          seed
        );
        setCeramic(authenticatedCeramic);

        const composeClientInstance = new ComposeClient({
          ceramic: authenticatedCeramic,
          definition: Composite,
        });
        setComposeClient(composeClientInstance);
      };

      initializeCeramic();
    }
  }, [userName]);

  const value = { ceramic, composeClient, userName, setUserName };

  return (
    <CeramicContext.Provider value={value}>{children}</CeramicContext.Provider>
  );
};

export const useCeramicContext = () => useContext(CeramicContext);

The error still arises, but I am stuck on how to give DID’s permission on the ability to update other DID’s data.

Hi @mpatel31,

What does the latest version of your generateSeed() function look like?

For what you’re trying to achieve, I would strongly suggest looking into did:pkh and did-session. This other recent thread might be of assistance, including links to sample code.

Hello @mohsin,

Here is my generateSeed function:

export const generateSeed = (name, length = 32) => {
  let existingSeed = getSeed(name);
  if (existingSeed) {
    return existingSeed; // Return existing seed if found
  }
  // No existing seed, generate a new one
  const seedBytes = CryptoJS.lib.WordArray.random(length).toString();
  const seedUint8Array = new Uint8Array(
    seedBytes.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))
  );
  // Save the newly generated seed under the given name
  saveSeed(name, seedUint8Array);

  return seedUint8Array;
};

The problem I am running into is not one of needing to authenticate the user, but that of giving other users access to edit data that user 1 created. So, if user 1 created data, like a profile. User 1 should give access to user 2 to edit user 1’s data/profile. But only a limited amount of data as well. Like only their name and not their password for example.

One DID can delegate permissions to another via Object Capabilities (OCAPs) but not for partial writes, as far as I know.

Typically, using did-session, a user may delegate permissions to an app to update streams owned by the user.

@ukstv, am I answering that correctly? Is this something that we could possibly have in the future, i.e. fine-grained OCAPs around individual fields?

I would not recommend storing passwords in Ceramic streams anyway. Even with encryption, it is risky to store credentials on any kind of public, immutable storage like Ceramic.

So, currently, there is no way for me to allow certain data to be manipulated? If I give access to another DID they have access to every data related to said DID?

That is true about the passwords, I was just stating it as an example.

No, not for every piece of data owned by the delegating DID.

Delegation includes a scope that will limit permissions to a particular model (ie. schema for data like User Profile).

What I meant was that you won’t be able to scope permissions to a single field within a model (e.g. just the user name and not the user avatar image).

I see.

What would you recommend if I only wanted certain data to be manipulated. Create different models that show the same data, but one model has more that is available?

Outside of data that can be manipulated, how would I go about giving access to other DIDs? Is there a page I should visit that talks about the OCAPs that you mentioned?

Please take a look at this other thread - it should point you to the right documentation for using OCAPs with did:key or did:pkh (through blockchain wallets). Let me know if that’s helpful?

It’s a good question about an OCAP that only allows editing specific fields. I’m tagging @paul to see if he has any suggestions here.

I don’t know if OCAPs support specifying only some fields, but even if it does I don’t think Ceramic supports it.
I think another solution would be to split the data in different documents using different models, and only grant permissions to the wanted documents.

2 Likes

Hey,
It does give me more context to the problem I am having! But, it doesn’t help with a solution. I keep running into the original error with all the updates that I have made.

Is this where I should be looking to see where I can give access to other did:key? It is the pointer demo that is mentioned for the other user.

Yes, that demo app would be a good place to start for delegating permissions from one did:key to another.

1 Like

Hello @mohsin

I have been looking at the example and other example apps provided on the documentation, but I am still struggling to find how exactly I should go about giving access to other DID’s. I feel like I am not looking in the right places, but I also am not sure if I have explained what I want to do.

This is the error that I am receiving when I am trying to edit data created on a different DID than the one I am ‘logged into.’ I believe the error is mainly referring to how the controller DID is not verified by the admin DID? I think the admin DID is the one that created the data and that needs to give access to the controller DID.

ERROR: Error: Can not verify signature for commit bagcqceraenylmkmzp5finw3l72odwn2jkjiwitnoiaisntrlei4l43gstsjq to stream kjzl6kcym7w8y5wcdrckt96q6itx8gqsaebib9jupgxvgg9er0s4lsybjhyq3c4 which has controller DID did:key:z6Mkmmnu5wykQXuW1z6xvBBG8HnHnWkJw75dGRUGHDcCdMTY: invalid_jws: not a valid verificationMethod for issuer: did:key:z6MktwHur1Vmz9v15js3ZbFsogBYZmizERRVrUgn5cGy6m72#z6MktwHur1Vmz9v15js3ZbFsogBYZmizERRVrUgn5cGy6m72

Is there a repo that I can look at the refers to authorization? Because this documentation shows me what I would like to do, but not the steps to do it, if that makes sense.