Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/remove-stellar-sdk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@crossmint/wallets-sdk": patch
---

Remove @stellar/stellar-sdk dependency by replacing it with tweetnacl (already a dependency) for ed25519 operations and a local Stellar StrKey encoder ported from open-signer.
1 change: 0 additions & 1 deletion packages/wallets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"@crossmint/common-sdk-base": "workspace:*",
"@hey-api/client-fetch": "0.8.1",
"@solana/web3.js": "1.98.1",
"@stellar/stellar-sdk": "v14.0.0-rc.3",
"abitype": "1.0.8",
"bs58": "5.0.0",
"@noble/hashes": "1.8.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { privateKeyToAccount } from "viem/accounts";
import { bytesToHex } from "@noble/hashes/utils";
import { Keypair as SolanaKeypair } from "@solana/web3.js";
import { Keypair as StellarKeypair } from "@stellar/stellar-sdk";

import type { Chain } from "../../../chains/chains";
import type { ServerSignerConfig } from "../../types";
import { deriveKeyBytes } from "../../../utils/server-key-derivation";
import { ed25519KeypairFromSeed, encodeStellarPublicKey } from "../../../utils/stellar";
import { getChainType } from "./get-chain-type";

export function deriveServerSignerAddress(keyBytes: Uint8Array, chain: Chain): string {
Expand All @@ -16,7 +16,7 @@ export function deriveServerSignerAddress(keyBytes: Uint8Array, chain: Chain): s
case "solana":
return SolanaKeypair.fromSeed(keyBytes).publicKey.toBase58();
case "stellar":
return StellarKeypair.fromRawEd25519Seed(Buffer.from(keyBytes)).publicKey();
return encodeStellarPublicKey(ed25519KeypairFromSeed(keyBytes).publicKey);
}
}

Expand Down
13 changes: 7 additions & 6 deletions packages/wallets/src/signers/server/stellar-server-signer.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Keypair } from "@stellar/stellar-sdk";
import type { Signer, ServerInternalSignerConfig } from "../types";
import { ed25519KeypairFromSeed, ed25519Sign, encodeStellarPublicKey } from "../../utils/stellar";

export class StellarServerSigner implements Signer<"server"> {
type = "server" as const;
private _address: string;
private _locator: string;
private keypair: Keypair;
private secretKey: Uint8Array;

constructor(config: ServerInternalSignerConfig) {
this.keypair = Keypair.fromRawEd25519Seed(Buffer.from(config.derivedKeyBytes));
this._address = this.keypair.publicKey();
const keypair = ed25519KeypairFromSeed(config.derivedKeyBytes);
this._address = encodeStellarPublicKey(keypair.publicKey);
this._locator = config.locator;
this.secretKey = keypair.secretKey;
}

address() {
Expand All @@ -26,8 +27,8 @@ export class StellarServerSigner implements Signer<"server"> {
throw new Error("StellarServerSigner.signMessage: expected a base64-encoded string");
}
const messageBytes = Buffer.from(message, "base64");
const signatureBytes = this.keypair.sign(messageBytes);
return { signature: signatureBytes.toString("base64") };
const signatureBytes = ed25519Sign(messageBytes, this.secretKey);
return { signature: Buffer.from(signatureBytes).toString("base64") };
}

async signTransaction(transaction: string) {
Expand Down
104 changes: 104 additions & 0 deletions packages/wallets/src/utils/stellar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import nacl from "tweetnacl";

/**
* Stellar StrKey encoding for ed25519 public keys.
* Ported from open-signer's shared/cryptography/src/primitives/encoding/strkey.ts
* to remove the @stellar/stellar-sdk dependency.
*/

const ED25519_PUBLIC_KEY_VERSION_BYTE = 6 << 3; // "G" prefix when base32-encoded

const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

// CRC16-XModem lookup table (from Stellar's implementation)
// prettier-ignore
const CRC16_TABLE = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad,
0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a,
0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,
0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861,
0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87,
0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3,
0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290,
0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e,
0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f,
0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83,
0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
];

function crc16Xmodem(data: Uint8Array): Uint8Array {
let crc = 0x0;
for (let i = 0; i < data.length; i++) {
const byte = data[i]!;
crc = ((crc << 8) ^ CRC16_TABLE[((crc >> 8) ^ byte)!]!) & 0xffff;
}
const checksum = new Uint8Array(2);
checksum[0] = crc & 0xff;
checksum[1] = (crc >> 8) & 0xff;
return checksum;
}

function base32Encode(data: Uint8Array): string {
let bits = 0;
let value = 0;
let output = "";

for (let i = 0; i < data.length; i++) {
value = (value << 8) | data[i]!;
bits += 8;
while (bits >= 5) {
output += BASE32_ALPHABET[(value >>> (bits - 5)) & 31];
bits -= 5;
}
}
if (bits > 0) {
output += BASE32_ALPHABET[(value << (5 - bits)) & 31];
}
while (output.length % 8 !== 0) {
output += "=";
}
return output;
}

/**
* Encode a 32-byte ed25519 public key as a Stellar StrKey address (G... format).
*/
export function encodeStellarPublicKey(publicKeyBytes: Uint8Array): string {
const payload = new Uint8Array(1 + publicKeyBytes.length);
payload[0] = ED25519_PUBLIC_KEY_VERSION_BYTE;
payload.set(publicKeyBytes, 1);

const checksum = crc16Xmodem(payload);
const unencoded = new Uint8Array(payload.length + 2);
unencoded.set(payload, 0);
unencoded.set(checksum, payload.length);

return base32Encode(unencoded);
}

/**
* Derive an ed25519 keypair from a 32-byte seed and return the public key bytes
* and a signing function compatible with the Stellar server signer.
*/
export function ed25519KeypairFromSeed(seed: Uint8Array): {
publicKey: Uint8Array;
secretKey: Uint8Array;
} {
const keypair = nacl.sign.keyPair.fromSeed(seed);
return { publicKey: keypair.publicKey, secretKey: keypair.secretKey };
}

/**
* Sign a message using ed25519 (detached signature).
*/
export function ed25519Sign(message: Uint8Array, secretKey: Uint8Array): Uint8Array {
return nacl.sign.detached(message, secretKey);
}
3 changes: 0 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading