Skip to content

Conversation

@speeddragon
Copy link

@speeddragon speeddragon commented Nov 27, 2025

Add support for ed25519 signing algorithm on:

  • ar_wallet (create, sign, and verify).
  • ar_bundles (L2 transactions).

Notes

Some transactions that use ed25519 might not be verified, because the GraphQL endpoint doesn't provide the anchor information required for validation. Eg: 1rTy7gQuK9lJydlKqCEhtGLp2WWG-GOrVo5JdiCmaxs

@speeddragon speeddragon marked this pull request as ready for review November 28, 2025 20:22
?assertEqual(Item1#tx.data, (maps:get(<<"key1">>, BundleItem#tx.data))#tx.data),
?assert(verify_item(BundleItem)).

eddsa_cases_test() ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be worth adding a test which deserializes and verifies a real eddsa data item pulled from the weave. You can download the raw dataitem and stick it in the test directory and then read/deserialize/verify in a test.

Earlier in our ans104 support we had only these sort of circular tests (use HB to generate and verify dataitems) and they all passed. But as soon as we started pulling real data from the weave everything broke (e.g. due to the little- vs big- encoding issue, or due to slight discrepancies in how the specs are handled). We still have an open issue due to HB and Arweave implementing ECDSA differently and incompatibly. Adding one test on real data should help avoid many of these issues or future regressions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless maybe you already have that in the hb_gateway_client test? I see that it is querying an ID - does it also verify the data?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first test I created, it used 1rTy7gQuK9lJydlKqCEhtGLp2WWG-GOrVo5JdiCmaxs information to verify the signature, but because the anchor isn't available, I provided it manually. But decided that since I was focus on testing the verify_item, I could just create a mock transaction signed with EDDSA, and it should be the same.

The only reason I removed it was because of the anchor, but I can add it back by providing the anchor and calling verify_item.

The test on the hb_gateway_client only checks other parameters of an ED25519 transaction, but doesn't verify it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I think this is what is being done in l2_dataitem_ed25519_test, @JamesPiechota . A sample (or a few) in the test/ directory and an associated eunit test sounds like a good idea in general, though.

@speeddragon : I don't see a hb_message:verify call on that test, I think? We should verify that a few validate correctly and that at least one form of invalid signature (ex: take a valid one, modify a bit in the body) fails.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I think I focus too much on the transaction verify function and forgot to check the support for the hb_message. I will take a look into it.

The tests on hb_gateway_client don't validate the message, so I didn't add it. There are tests under dev_message that do the validations, I will look into them.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are tests under dev_message that do the validations, I will look into them.

The main codec tests are in the hb_message_test_vectors module. The dev_message ones are only really to verify that the calls to the codecs are hooked up correctly. Could you setup a new codec definition (with type: NEW_SIG_SCHEME?) in the test vectors, then we can just check that all of those tests pass? The suite is quite exhaustive at this point.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've modified the dev_message:verify_test to support both wallets. I don't think modifying hb_message_test_vectors would make sense for this case.

Also added the data item of 1rTy7gQuK9lJydlKqCEhtGLp2WWG-GOrVo5JdiCmaxs which I extracted from the bundle. This has the anchor information, and we can deserialize and verify it.

ID = <<"oyo3_hCczcU7uYhfByFZ3h0ELfeMMzNacT-KpRoJK6g">>,
{ok, Res} = read(ID, #{}),
?event(gateway, {l2_dataitem, Res}),
CommitmentType = hb_util:deep_get([<<"commitments">>, ID, <<"type">>], Res, not_found),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please always pass an Opts when making calls to the hb_* modules. If you don't, you can often lose validity of the data you have in-context because lazy links will be resolved against the wrong store.

l2_dataitem_test() ->
_Node = hb_http_server:start_node(#{}),
{ok, Res} = read(<<"oyo3_hCczcU7uYhfByFZ3h0ELfeMMzNacT-KpRoJK6g">>, #{}),
ID = <<"oyo3_hCczcU7uYhfByFZ3h0ELfeMMzNacT-KpRoJK6g">>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why unwrap this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test to validate commitment type in l2_dataitem_ed25519_test. Since I didn't find any test with the RSA key, I've also added this check on one of the other tests. Since I need the ID to access to the value inside the message, I decided to extract the binary to a variable to only write the ID value once.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ok, Res} = read(ID = <<"oyo3_hCczcU7uYhfByFZ3h0ELfeMMzNacT-KpRoJK6g">>, #{}), is the general way we do things like this, if the lines don't become unwieldy.

@speeddragon speeddragon changed the title Add ed25519 support feat: Add ed25519 support Dec 2, 2025

to_eddsa_address(PubKey) ->
Hash = crypto:hash(sha256, PubKey),
base64:encode(Hash, #{padding => false, mode => urlsafe}).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed that we don't encode the RSA address, but it looks like we do encode EDDSA and maybe ECDSA? Any idea if this is intentional? I could see the ambiguity creating problems downstream

Copy link
Author

@speeddragon speeddragon Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The right way is without the encode. Most of the cases when ar_wallet:to_address is used are followed by hb_util:human_id, which calls b64fast:encode. I will fix it, thanks!

EDIT: Regarding ECDSA, I will not change anything for now. I would need to investigate this further, and there are high-priority tasks I need to attend to.

}
);
sign({KeyType = {KeyAlg, Curve}, Priv, _Pub}, Data, _DigestType) when KeyType =:= {?EDDSA_SIGN_ALG, ed25519} ->
crypto:sign(KeyAlg, none, Data, [Priv, Curve]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super familiar with crypto:sign, do you know why we aren't passing DigestType into crypto:sign?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question. ed25519 digest the message in its raw form. Providing none or sha256 doesn't affect the output.

From Google AI:

EdDSA uses two main digest types: PureEdDSA and HashEdDSA. PureEdDSA signs the message directly without a prior hash, while HashEdDSA first hashes the message using a collision-resistant hash function (like SHA-512).

HashEdDSA can be ed25519ph.

@speeddragon speeddragon force-pushed the verify_ed25519_messages branch from 3d08950 to 21936fc Compare December 17, 2025 10:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants