specs icon indicating copy to clipboard operation
specs copied to clipboard

IPNS: support Ethereum wallet signing

Open paulgmiller opened this issue 3 years ago • 10 comments

I think it would be pretty useful if ipns recognized the signing prefix used by ethereum wallets. That way users with metamask or other wallets could sign ipns records to send to trusted nodes or providers like cloudflare, web3-storage, pinata without having to manage their own set of keys.

I think this would open up some interesting possibilities of distributed blogs other frequently updated content.

I think this would be pretty possilble to do in both go|js-ipns. Been poking around with a branch that can validate thos sigs But if desired not sure if it would be best to

  1. Always check the eth prefix.
  2. Make this a new nodeid in libp2p so we could go off its type.
  3. use some other data in the ipns record to indicate it's a eth signed record.

Happy to try and contribute if wanted and someone is willing to guide preferences.

Some background here

paulgmiller avatar Sep 21 '22 05:09 paulgmiller

Thank you for filling this. go-ipns repository is not the right place to propose this change. I am moving this to ipfs/specs repo :)


FYSA the modern IPNS specification, including details how records are created and validated, is being documented in https://github.com/ipfs/specs/pull/319. Give it a read – that PR documents how go-ipns and js-ipns work today.

See "Backward Compatibility" section to understand the constraints and our policy around not breaking legacy clients (tldr: IoT device must be able to fetch and validate IPNS record with firmware update).

Something you may find useful, are Extensible IPNS Records. IPNS records have IpnsEntry.data field, which is an extensible DAG-CBOR document. Implementations are free to put additional data there, such as additional audit information.

Our libraries do not expose easy way of adding fields to IpnsEntry.data yet, but the specification and the wire format already support it. Sounds like this is the best place for storing these additional signatures.

lidel avatar Sep 21 '22 22:09 lidel

THanks for the link to #319 the existing spec didn't say much about data (nor did it talk about the v2 sig thats in the code). So excited to give this a look!

paulgmiller avatar Sep 23 '22 04:09 paulgmiller

Left a comment on #319 could totally see how we could add extra CBOR fields.

SignaturePrework : Ethereum or seperate fields SignatureHash: None | keccak256 SignaturePrefix: \x19Ethereum Signed Message:\n

But those would be replacements not additions to signature v2 for those records becasue we wallets won't give out access to the private keys in any reasonable UI flow.

Would it be fine for only some nodes accept and circulate these records? Is it different from adding a new Public Key type in the future?

Thanks again.

https://github.com/ethereum/go-ethereum/commit/b59c8399fbe42390a3d41e945d03b1f21c1a9b8d

paulgmiller avatar Sep 23 '22 04:09 paulgmiller

Would it be fine for only some nodes accept and circulate these records?

I mean, the network does not care – it will simply ignore these additional fields. If the IPNS signature is invalid, records are simply ignored. Same for unknown key types.

It is more a question of your use case: If you are creating software that will both create and validate this additional metadata, then you can do whatever you want: as long you also include a valid IPNS signature that is in the spec, DHT nodes and PubSub routers will accept IPNS PUT your software does.

lidel avatar Oct 23 '22 18:10 lidel

@paulgmiller, some additional notes around "signing IPNS record with Metamask" use case. Apologies for wall of text, but I wanted to give a comprehensive explainer that we could reference in the future.

On signing IPNS records with Metamask

Discussed this with @2color today in the context of https://github.com/ipfs/js-ipns/pull/192 (making it possible to sign valid IPNS records with secp256k1 keys outside of IPNS node) and the underlying problem is that even though IPNS supports signing records with secp256k1 keys, the way these signatures are performed is different than one (out of four+ ways) signing happens in Ethereum.

Multiple types of secp256k1 signatures

iiuc Ethereum and IPNS use different hash functions and prefix signed payload with different bytes:

Ethereum (metamask) signature with secp256k1 (one of four options?) IPNS signature (V2) with secp256k1
signA(keccak256(\x19Ethereum Signed Message:\n + len(data) + data)) sign_bip62_der(sha256(ipns-signature: + data))
signA() defined by EIP-712 (v4)
(but it is similar story for 4 other types listed here)
sign_bip62_der() defined by BIP0062, see libp2p specs

IPNS should not depend on any blockchain specifics

Given the above, we have incompatible signatures. One could argue IPNS spec could be extended to with Ethereum-specific signatures, but why should we tie IPNS spec to Ethereum-specific signatures, and not Some Other Blockchain?

This raises even more questions:

  • how to signal when different signature for the same key type should be used?
  • Should we add multiple versions of the same key type, to remove this ambiguity around how to create signature?
  • How do we make the decision which chain-specific-signature-flavour is "worthy" and which one is not?
  • Why do we shift the implementation burden to IPNS implementers who don't care about all these blockchains?

Finally, Ethereum ecosystem seems to have competing signature standards: there are 4 standards, and they already discuss adding v5. This does not bring a lot of confidence into stability and interoperability.

:point_right: I think all these questions and rabbit holes should be avoided. IPNS signatures must be chain-agnostic.

IPNS is routing-agnostic (records can be resolved via DHT, PubSub, and soon Gateways).

We allow multiple key types, but the way signatures are created must remain IPFS-specific to

  1. avoid the discussion which blockchain is "worthy",
  2. avoid ballooning complexity on the client side by requiring support for multiple key types (and / or signature types)
  3. ensure signatures can't be used outside of IPNS record context

What are the next steps?

For "signing IPNS with Metamask" use case, I see three paths forward:

  • Dual signing with two sets of keys (no change in Metamask, no change in IPNS spec, but second signature is only used by Ethereum-aware clients).

    • Metamask signs DAG-CBOR in IpnsEntry.data, and we then put that signature inside something like IpnsEntry.data[ethereum_personal_sign] itself and sign entire IpnsEntry.data with IPNS-specific key and prefix and store result in signatureV2
    • IPFS clients will ignore Ethereum-specific signature present in IpnsEntry.data, but clients who care about Ethereum identity could verify this second signature for additional confidence or UX.
    • :green_circle: This could work today, requires minimal work in libraries like js-ipns to expose ability to modify CBOR in IpnsEntry.data before signing. And it would work with Ledger (which seems to only support personal_sign method)
  • Signing using current IPNS signatureV2 spec (no change in IPNS spec, requires Metamask update)

    • For signed records to be interpreted as valid by IPFS nodes today, one needs to create a record with IPNS-spec compliant signatureV2), which for secp256k1 means doing: sign_bip62_der(sha256(ipns-signature: + data))
    • :green_circle: This could be implemented as another (they already support, so :shrug:) in Metamask, as long their software wallet is used.
    • :red_circle: This won't be possible with Ledger and other hardware wallets that only implement personal_sign method – they would have to implement IPNS-specific signing method.
  • Modify IPNS specification (hard way, requires changes across specs and IPNS implementations, and if it is chain-agnostic, also Metamask)

    • Leverage the IPIP process: new chain-agnostic signature standard(s) could be proposed as IPIP
    • This is long term work, requires buy-in from multiple stakeholders to ensure both adoption and interoperability.

lidel avatar Dec 13 '22 20:12 lidel

This is a great write up and way more than I expected from this issue. Thank you very much. I totally get the concerns about ipns not chaining itself to any of the many blockchain standards maybe if that stabilizes it coming years there might be a defacto standard but probably too early to tell.

Speaking towards the next steps.

I think the main issue with "dual signing" is that ipfs nodes will only keep the latest revision so an ipfs node has to generate a new key for each "eth/metamask" user that wants to use it otherwise it will only be able to serve one forieng user. So if that user moves between nodes it'll be lost what public key to look up. So you need another global source of truth like ens/dns to be updated when that happens.

For "using current ipns spec" you need not just metamask but all wallets to pick this up doable but very long term in my mind.

My current plan is to just create an application specific version of ipns that publishes via pubsub so apps that want this can get it. The BIG downside of this is only apps that know about it participate. You can't get all the ipfs nodes to participate. But it does let us get ipns pointers passed around and we can come back to "modify ipns spec" if there is every any successful app here.

Thanks again!

paulgmiller avatar Dec 19 '22 06:12 paulgmiller

Wow, there's a lot to take in here! I would recommend, before picking a path and getting to work, doing a tiny bit of research on the metamask Snaps project and its custom signing and deterministic key derivation capabilties! Snaps are basically MM plug-ins, and AFAIK the preferred way for external teams to extend metamask signing and key handling capabilities...

bumblefudge avatar Dec 19 '22 18:12 bumblefudge

To elaborate on (or summarise) what @lidel mentioned, all current signing methods in the Ethereum ecosystem ((https://eips.ethereum.org/EIPS/eip-191 and https://eips.ethereum.org/EIPS/eip-712)) are unsuitable for interoperability with IPNS for two reasons:

  • They hash using keccak256 before signing — this hash function is currently not supported by the IPFS/libp2p libraries
  • For security reasons, signed messages have an Ethereum specific prefix: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))). for security reasons.

This leads to the three options laid out:

  • Dual signing:
    • this one adds another layer of complexity to IPNS, which is arguably already suffering from complexity creep
    • (as an example, you need both protobuf and CBOR and two signature types to serialise a record)
    • This would add a third signature 😱
    • Current nodes in the IPFS network would only verify the native IPNS signature, so this would achieve the level of security that one would expect from a self-certifying system.
  • Signing using current IPNS signatureV2 spec
    • This is interesting to explore because we might be able to tap into existing Bitcoin wallet users, assuming they expose a signing method compatible with libp2p signatures for Secp256k1 keys.
    • I went a bit down the rabbit hole here to understand what signature scheme Bitcoin uses and whether there are actual standards.
    • The libp2p specs claim to implement the BIP-62, that BIP was withdrawn: https://github.com/bitcoin/bips/blob/15c8203eb36304efa1e4588b950f62a5bb32f965/bip-0062.mediawiki#L10
    • There's another BIP for generic signed messages: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki this one seems to be gaining traction with an active effort in Bitcoin Core.
    • As it turns out, Bitcoin supports different kinds of addresses for historical reasons and there are likely quirks and nuances when creating signatures for the different kinds. See https://b10c.me/blog/006-evolution-of-the-bitcoin-signature-length/
    • Some related discussions: https://github.com/trezor/trezor-mcu/issues/169 https://github.com/bitcoin/bitcoin/issues/10542 https://github.com/bitcoin/bitcoin/pull/24058
    • Ledger has a signMessage method https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledgerjs/packages/hw-app-btc#signmessage for the BTC app but it's not clear which spec it implements. The docs just say Bitcoin Signature format.
    • I don't have a bug takeaway besides that this needs to be investigated further.
  • Modify the IPNS spec
    • This is probably the path we'll want to follow with this in the long term to create more interoperability and allow users to reuse keys they already manage.
    • If the goal is to make IPNS more accessible to users, we need more data and research into how users manage their keys
    • The WebCrypto API might also be worth investigating https://w3c.github.io/webcrypto/#ecdsa as it supports ECDSA

I'll be getting in touch with the Metamask folks via CASA to make them aware and investigate some possible future paths.

2color avatar Dec 20 '22 12:12 2color

Hello from an engineer working on MetaMask Snaps.

There have been quite a few requests to us for different cryptographic primitives and signature schemes, including JWT tokens, Bitcoin signatures, IPLD through CACAO, plain RSA, etc.

We can't support all of them due to their sheer count.

What we're trying to achieve is to allow the community to agree on common standards themselves and then allow people to extend MetaMask using Snaps to support that specific use-case.

We're allowing Snaps access to raw private key entropy so a Snap that provides IPNS signatures is possible.


In short, I'm suggesting it might be easier to write a plugin for MetaMask to support IPNS rather than updating the IPNS spec to support MetaMask, and our team will do everything what we can support you on that route.

ritave avatar Jan 12 '23 17:01 ritave

Thanks for the update @ritave

I suppose the Filsnap might be worth looking at for how to do so signing with Metamask Snaps, e.g. https://github.com/ChainSafe/filsnap/blob/master/packages/snap/src/rpc/signMessage.ts#L102

2color avatar Jan 16 '23 12:01 2color