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.