rust-bitcoin / rust-secp256k1

Rust language bindings for Bitcoin secp256k1 library.
Creative Commons Zero v1.0 Universal
346 stars 267 forks source link

Link dynamically with system-provided libsecp256k1 instead of bundling #629

Open jirutka opened 1 year ago

jirutka commented 1 year ago

The secp256k1 readme says:

If you want to compile this library without using the bundled symbols (which may be required for integration into other build systems), you can do so by adding --cfg=rust_secp_no_symbol_renaming' to your RUSTFLAGS variable.

The mentioned config option just disables symbols renaming, it doesn’t affect the build script. And the links attribute in Cargo.toml defines a non-standard name rustsecp256k1_v0_8_1. Should I build it with the following in .cargo/config.toml or how?

[target.<target>]
rustsecp256k1_v0_8_1 = { rustc-link-lib = ["secp256k1"] }

Related issues and PRs: #380 #189

apoelstra commented 1 year ago

What is a "standard" links attribute value?

And I would strongly strongly discourage anybody from trying to link this library against whatever version of libsecp256k1 happens to exist on their system. libsecp256k1 only had a real release for the first time this year, and prior to that, distributions would just bundle random git commits from the repository with no input or approval from the libsecp maintainers.

At some point we should do a survey and see if all the major distributions have switched to bundling released versions of the library, and then we could investigate this. But we'd still have to handle the case that the library isn't present (or is the wrong version) which cargo can't affect.

jirutka commented 1 year ago

What is a "standard" links attribute value?

It should match the base name of the (native) library (without lib prefix and .so*) under which it’s normally installed on the system; that’s secp256k1. Using the name of the rust binding instead of the linked library quite negates the point of this attribute.

And I would strongly strongly discourage anybody from trying to link this library against whatever version of libsecp256k1 happens to exist on their system.

This is used mainly by package maintainers in Linux distributions. They have control over what version of the library it’s linked with, so it’s not just whatever version.

libsecp256k1 only had a real release for the first time this year, and prior to that, distributions would just bundle random git commits from the repository with no input or approval from the libsecp maintainers.

Well, that’s very inconvenient, but fortunately, it’s in the past; now they do releases and declare SONAME (and hopefully understand ABI compatibility…). BTW, please don’t blame the package maintainers for picking up random commits from the repository when there were no releases for such a long time, so the libsecp maintainers gave them no choice.

At some point we should do a survey and see if all the major distributions have switched to bundling released versions of the library

You can see it here: https://repology.org/project/libsecp256k1/versions.

Alpine Linux already packages released versions of libsecp256k1, so we could link rust-secp256k1 against shared system library instead of bundling it (which is very undesirable, especially in the case of crypto libraries).

apoelstra commented 1 year ago

Ok, thanks for explaining. I'd like to figure this out, but it's difficult because crates.io/cargo don't give us a lot of great options.

Using the name of the rust binding instead of the linked library quite negates the point of this attribute.

If we then had two major versions of the bindings, which both linked to the same underlying C code (or at least, two versions with the same soname), then it would be impossible for both to exist in the same cargo tree, breaking compilation for people with certain dependency combinations. (You can see this in the discussion of #189, which you linked, and related issues.)

AFAICT preventing this is the point of the link name, and us sticking the Rust binding version number into there accomplishes it.

This is used mainly by package maintainers in Linux distributions. They have control over what version of the library it’s linked with, so it’s not just whatever version.

Ah, ok, and I guess here is where it gets frustrating that we have an ad-hoc-looking name in link.

So, unfortunately the crates.io ecosystem, which we are more-or-less forced to support, makes it difficult for use to simultaneously support Debian maintainers. In particular:

BTW, please don’t blame the package maintainers for picking up random commits from the repository when there were no releases for such a long time, so the libsecp maintainers gave them no choice.

From the libsecp maintainers' perspective, libsecp was an internal component of Bitcoin Core (which did, and always will, bundle its own vendored copy), which we discouraged people to use outside of Core. I appreciate that when people did do this nonetheless, package maintainers were put in a difficult position, but it was also an unfair burden on us to need to commit to a well-defined ABI when we were still iterating on major parts of the library internals.

apoelstra commented 1 year ago

@jirutka can you describe what you would like to happen? e.g. if we changed the build script to just disable building the bundled library when rust_secp_no_symbol_renaming is set, would that be sufficient?

Knowing what you need would help me figure out how to do it in the least-disruptive way to cargo users.

Kixunil commented 1 year ago

Since I recently asked a similar question (#657 ) I went into the rabbit hole of understanding linking in Rust.

According to the documentation overriding build scripts should be the right way to link to system libraries. However, cargo/rustc does not provide any protection mechanism to prevent accidental linkage against a wrong version. I'm not sure how bad this is.

Also I think links key should really have value secp256k1 when renaming is not used. We can't change it dynamically though.

I suspect the correct way of doing things would be to compile and statically link the code inside the non-sys crate and only when secp256k1-sys feature is on skip this and use that one. This at least keeps existing use case of linking multiple versions of the library working but also support globally move over to dynamically linked version.

The -sys build script can have a compile-time check of library version (by soname or whatever) but if anyone tediously overrides it with .cargo/config they're out of luck.

Just one thing that annoys me is the inability to prevent cc from compiling if dynamic linking is used.

Side note: it might be nicer to use bindgen but that would completely disable overriding of build scripts.

apoelstra commented 1 year ago

bindgen would produce code that was less readable and yet would require much more careful review because no human was in the loop.

As for linking, I'm not quite sure what you're proposing. It sounds like:

We should also do some sort of startup check in the library to check that the correct version of secp256k1 is available at runtime, though (I think) there aren't any non-malicious colliding SONAMEs out there anymore. And of course there's nothing we can do about malicious ones.

Just one thing that annoys me is the inability to prevent cc from compiling if dynamic linking is used.

In theory we could make the cc dep depend on the secp256k1-sys feaature and feature-gate all our calls to it? But I think that, even if the feature is turned off, we should fall back to static linking so that the library continues to work.

Kixunil commented 1 year ago

much more careful review because no human was in the loop.

It'd require less review because there's no human. Humans make more mistakes than machines.

Otherwise, fall back to the static library.

No such fallback, -sys means "please link to system library".

But I got confused and used "static library" to mean vendored and "dynamic" meaning system library. There's .a in libsecp256k1-dev on Debian.

We can't feature gate cc because it needs to be on by default to enable building and it's impossible to turn off dependencies based on features. But there's another way: have secp256k1-vendored which we will depend on (can still be optional, on-by-default) but will be easy to patch-away using a dummy (empty) crate if users want to avoid dealing with it without going around and asking all maintainers to modify features.

apoelstra commented 1 year ago

No such fallback, -sys means "please link to system library".

Oh, I see. This sounds good, though it's confusing because the static library is called -sys.

But there's another way: have secp256k1-vendored which we will depend on (can still be optional, on-by-default) but will be easy to patch-away using a dummy (empty) crate if users want to avoid dealing with it without going around and asking all maintainers to modify

This sounds good, though I'm not sure exactly what it would look like.