Description
`chainAdapters.solana.Solana.deriveAddressAndPublicKey()` throws `"Non-base58 character"` because the underlying `ChainSignatureContract.getDerivedPublicKey()` always converts its result through `K()` (uncompressed SEC1 hex), even for Ed25519 keys.
The Solana adapter then strips a non-existent `"ed25519:"` prefix from the hex string and base58-decodes the leftover hex:
async deriveAddressAndPublicKey(t, e) {
let r = (await this.contract.getDerivedPublicKey({path: e, predecessor: t, IsEd25519: true}))
.replace("ed25519:", "");
let i = bs58.decode(r); // ← decoding hex as base58 fails
let a = new PublicKey(i);
...
}
Meanwhile `getDerivedPublicKey` does:
async getDerivedPublicKey(t) {
let e = await this.provider.callFunction(this.contractId, "derived_public_key", {
path: t.path, predecessor: t.predecessor, domain_id: t.IsEd25519 ? 1 : 0
});
return K(e) // ← always converts to "04..." hex
}
The TypeScript signature claims Promise<UncompressedPubKeySEC1 | \Ed25519:${string}`>` but the implementation only returns the SEC1 form.
Reproduction
On mainnet, with `v1.signer` as the contract (which supports both domains):
import { contracts, chainAdapters } from "chainsig.js";
import { Connection } from "@solana/web3.js";
const contract = new contracts.ChainSignatureContract({
contractId: "v1.signer",
networkId: "mainnet",
});
const adapter = new chainAdapters.solana.Solana({
solanaConnection: new Connection("https://api.mainnet-beta.solana.com"),
contract,
});
await adapter.deriveAddressAndPublicKey("near", "solana-1");
// → Error: Non-base58 character
Verified by direct RPC call that `v1.signer.derived_public_key({domain_id: 1, ...})` returns a valid `"ed25519:"` string. The bug is purely in the chainsig.js conversion step.
Affected versions
`1.1.14` (latest at time of report).
Workaround
Bypass `getDerivedPublicKey` for Solana and call the MPC contract directly. The base58 part of `ed25519:` IS the Solana address:
const raw = await provider.callFunction(mpcContract, "derived_public_key", {
path, predecessor, domain_id: 1,
}); // "ed25519:<base58>"
const address = raw.replace(/^ed25519:/, "");
Suggested fix
`getDerivedPublicKey` should branch on `IsEd25519` and return the raw `"ed25519:..."` string for Ed25519 keys (or return a discriminated union), then the Solana / Aptos / SUI adapters can consume it correctly.
Found while building near-hydra — happy to send a PR if helpful.
Description
`chainAdapters.solana.Solana.deriveAddressAndPublicKey()` throws `"Non-base58 character"` because the underlying `ChainSignatureContract.getDerivedPublicKey()` always converts its result through `K()` (uncompressed SEC1 hex), even for Ed25519 keys.
The Solana adapter then strips a non-existent `"ed25519:"` prefix from the hex string and base58-decodes the leftover hex:
Meanwhile `getDerivedPublicKey` does:
The TypeScript signature claims
Promise<UncompressedPubKeySEC1 | \Ed25519:${string}`>` but the implementation only returns the SEC1 form.Reproduction
On mainnet, with `v1.signer` as the contract (which supports both domains):
Verified by direct RPC call that `v1.signer.derived_public_key({domain_id: 1, ...})` returns a valid `"ed25519:"` string. The bug is purely in the chainsig.js conversion step.
Affected versions
`1.1.14` (latest at time of report).
Workaround
Bypass `getDerivedPublicKey` for Solana and call the MPC contract directly. The base58 part of `ed25519:` IS the Solana address:
Suggested fix
`getDerivedPublicKey` should branch on `IsEd25519` and return the raw `"ed25519:..."` string for Ed25519 keys (or return a discriminated union), then the Solana / Aptos / SUI adapters can consume it correctly.
Found while building near-hydra — happy to send a PR if helpful.