Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix!: only accept CIDs, PeerIds or strings as values #255

Merged
merged 4 commits into from
Sep 12, 2023

Conversation

achingbrain
Copy link
Member

This module has accepted Uint8Arrays as values to store in IPNS
records which means it has been mis-used to create records with
raw CID bytes.

To ensure this doesn't happen in future, accept only CIDs, PeerIds
or arbitrary path strings prefixed with "/".

This module has accepted `Uint8Array`s as values to store in IPNS
records which means it has been mis-used to create records with
raw CID bytes.

To ensure this doesn't happen in future, accept only CIDs, PeerIds
or arbitrary path strings prefixed with `"/"`.
@achingbrain
Copy link
Member Author

achingbrain commented Aug 30, 2023

@hacdias @lidel what do you think to tightening up the inputs here, since #234 is a breaking change.

I think this module has only ever accepted Uint8Arrays for ...reasons* - making the value explicitly CID | PeerId | string should make it harder to mis-use in the future?

*= the reason is probably because the protobuf definition has bytes and not string for the record value

@hacdias
Copy link
Member

hacdias commented Sep 4, 2023

@achingbrain I do like the idea. I'm just concerned about the corner case where a CID is actually a libp2p-key encoded Peer ID. Then we'd be generating a /ipfs/{libp2p-key} instead of a /ipns/{libp2p-key-cid}. I'm not sure if more or less magic would be the best at this point. Sadly, there's no path type to take this issues away.

@achingbrain
Copy link
Member Author

No, but it sounds like an edge case we can just handle. I'd love to get to the point where we can just treat these values as opaque and the module would just do the right thing rather than the user having to know that some types have to be encoded some ways and other types have to be encoded other ways.

@achingbrain
Copy link
Member Author

FWIW Kubo doesn't seem to handle this edge case - trying to publish a peer ID encoded as a CID gives HTTPError: could not choose a decoder: no decoder registered for multicodec code 114 (0x72)

src/utils.ts Outdated
Comment on lines 264 to 268
* Normalizes the given record value. It ensures it is a PeerID, a CID or a
* string starting with '/'. PeerIDs become `/ipns/${peerId}`, CIDs become
* `/ipfs/${cidAsV1}`.
*/
export const normalizeValue = (value: string | Uint8Array): string => {
const str = typeof value === 'string' ? value : uint8ArrayToString(value)
export const normalizeValue = (value?: CID | PeerId | string | Uint8Array): string => {
Copy link
Member

@lidel lidel Sep 7, 2023

Choose a reason for hiding this comment

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

nit: iirc we try to do opposite and want to remove use of PeerIDs in IPNS context, as it causes bugs where someone tries to parse 12.. one as CID (example) .

In various places we've been normalizing to CIDv1 with libp2p-key and base36 (this way it looks the same on subdomains, and implementers only need to support CIDs, which is more robust for interop).

@achingbrain would it be ok to switch /ipns/ paths to use CIDv1 with libp2p-key? (base does not matter as much as CIDv1)

Copy link
Member Author

Choose a reason for hiding this comment

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

we try to do opposite and want to remove use of PeerIDs in IPNS context

What I really want to do here is take a PeerID and do the right thing, rather than relying on users to know what the right thing to do is.

it causes bugs where someone tries to parse 12.. one as CID (example) .

Do you mean people are extracting 12foo out of /ipns/12foo and trying to decode it as a CID? Seems like user error rather than a bug if so.

would it be ok to switch /ipns/ paths to use CIDv1 with libp2p-key?

Do you mean accept a PeerId and output "/ipns/base32-encoded-libp2p-key-cid" ?

Maybe outputting "/ipfs/base32-encoded-libp2p-key-cid" would be better? Will Kubo resolve IPNS values like this recursively?

Then {thing} in /ipns/{thing} is always a base58btc encoded PeerID and {thing} in /ipfs/{thing} is always a CID (which could be a libp2p-key)?

Copy link
Member

@hacdias hacdias Sep 7, 2023

Choose a reason for hiding this comment

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

@achingbrain I think the goal moving forward into the future is to always refer to /ipns/{libp2p-encoded-cid} and not as a base56btc: https://specs.ipfs.tech/ipns/ipns-record/#string-representation, including on the gateway.

The current gateway implementation does the redirect even.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, as long as we're fine with it being /ipns/{maybe-CID,maybe-base58btcpeerid} I guess?

@achingbrain
Copy link
Member Author

@lidel @hacdias I've switched /ipns/* to always be normalised to /ipns/{base36-encoded-cidv1-libp2p-key}

Copy link
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

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

@achingbrain sgtm, pushed jsdoc update to reflect that its cidV1.

See comment inline about also testing against static vectors from ipfs/gateway-conformance#157

Comment on lines +216 to +221
it('should fail to normalize path value that is too short', async () => {
const inputValue = '/'

await expect(ipns.create(peerId, inputValue, 0, 1000000)).to.eventually.be.rejected
.with.property('code', ERRORS.ERR_INVALID_VALUE)
})
Copy link
Member

Choose a reason for hiding this comment

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

ℹ️ sgtm, forces people to provide value under a meaningful namespace 👍

Comment on lines +290 to +298
// if we have a CID, turn it into an ipfs path
const cid = CID.asCID(value)
if (cid != null) {
// PeerID encoded as a CID
if (cid.code === LIBP2P_CID_CODEC) {
return `/ipns/${cid.toString(base36)}`
}

return `/ipfs/${cid.toV1().toString()}`
Copy link
Member

Choose a reason for hiding this comment

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

ℹ️ good quality of life improvement, thanks 👍

Copy link
Member

Choose a reason for hiding this comment

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

💭 slight concern is that (iiuc) we are generating test vectors on the fly using JS functions from src/utils.ts, and if we change JS code at any point in the future, we may not catch regression until there is a hiccup somewhere in production when interacting with other implementation.

@achingbrain thoughts on including tests against binary fixtures /fixtures/ipns_records/*.ipns-record in
ipfs/gateway-conformance#157? This will be a solid way to ensure interop with GO and any other languages. We could do it in follow-up PR but

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good!

@achingbrain achingbrain merged commit cedb3f2 into validate-v2 Sep 12, 2023
16 checks passed
@achingbrain achingbrain deleted the fix/tighten-up-input-types branch September 12, 2023 16:14
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.

3 participants