UNPKG

@stellar/stellar-sdk

Version:

A library for working with the Stellar network, including communication with the Horizon and Soroban RPC servers.

288 lines (285 loc) 9.22 kB
import { Buffer } from 'buffer'; import * as ed from '@noble/ed25519'; import { sha512 } from '@noble/hashes/sha2.js'; import { generate, sign, verify } from './signing.js'; import { StrKey } from './strkey.js'; import { hash } from './hashing.js'; import types from './generated/curr_generated.js'; ed.hashes.sha512 = sha512; const MESSAGE_PREFIX = Buffer.from("Stellar Signed Message:\n", "utf8"); class Keypair { type; _publicKey; _secretSeed; _secretKey; /** * @param keys - at least one of keys must be provided. * - `type`: public-key signature system name (currently only `ed25519` keys are supported) * - `publicKey`: raw public key * - `secretKey`: raw secret key (32-byte secret seed in ed25519) */ constructor(keys) { if (keys.type !== "ed25519") { throw new Error("Invalid keys type"); } this.type = keys.type; if ("secretKey" in keys) { keys.secretKey = Buffer.from(keys.secretKey); if (keys.secretKey.length !== 32) { throw new Error("secretKey length is invalid"); } this._secretSeed = keys.secretKey; this._publicKey = generate(keys.secretKey); this._secretKey = keys.secretKey; if (keys.publicKey && !this._publicKey.equals(Buffer.from(keys.publicKey))) { throw new Error("secretKey does not match publicKey"); } } else if ("publicKey" in keys) { this._publicKey = Buffer.from(keys.publicKey); if (this._publicKey.length !== 32) { throw new Error("publicKey length is invalid"); } } else { throw new Error( "At least one of publicKey or secretKey must be provided" ); } } /** * Creates a new `Keypair` instance from secret. This can either be secret key or secret seed depending * on underlying public-key signature system. Currently `Keypair` only supports ed25519. * @param secret - secret key (ex. `SDAK....`) */ static fromSecret(secret) { const rawSecret = StrKey.decodeEd25519SecretSeed(secret); return this.fromRawEd25519Seed(rawSecret); } /** * Creates a new `Keypair` object from ed25519 secret key seed raw bytes. * * @param rawSeed - raw 32-byte ed25519 secret key seed */ static fromRawEd25519Seed(rawSeed) { return new this({ type: "ed25519", secretKey: rawSeed }); } /** * Returns `Keypair` object representing network master key. * @param networkPassphrase - passphrase of the target stellar network (e.g. "Public Global Stellar Network ; September 2015") */ static master(networkPassphrase) { if (!networkPassphrase) { throw new Error( "No network selected. Please pass a network argument, e.g. `Keypair.master(Networks.PUBLIC)`." ); } return this.fromRawEd25519Seed(hash(networkPassphrase)); } /** * Creates a new `Keypair` object from public key. * @param publicKey - public key (ex. `GB3KJPLFUYN5VL6R3GU3EGCGVCKFDSD7BEDX42HWG5BWFKB3KQGJJRMA`) */ static fromPublicKey(publicKey) { const publicKeyBuffer = StrKey.decodeEd25519PublicKey(publicKey); if (publicKeyBuffer.length !== 32) { throw new Error("Invalid Stellar public key"); } return new this({ type: "ed25519", publicKey: publicKeyBuffer }); } /** * Create a random `Keypair` object. */ static random() { const secretKey = ed.utils.randomSecretKey(); return this.fromRawEd25519Seed(Buffer.from(secretKey)); } /** Returns this public key as an xdr.AccountId. */ xdrAccountId() { return types.PublicKey.publicKeyTypeEd25519(this._publicKey); } /** Returns this public key as an xdr.PublicKey. */ xdrPublicKey() { return types.PublicKey.publicKeyTypeEd25519(this._publicKey); } /** * Creates a {@link xdr.MuxedAccount} object from the public key. * * You will get a different type of muxed account depending on whether or not * you pass an ID. * * @param id - (optional) stringified integer indicating the underlying muxed * ID of the new account object */ xdrMuxedAccount(id) { if (typeof id !== "undefined") { if (typeof id !== "string") { throw new TypeError(`expected string for ID, got ${typeof id}`); } return types.MuxedAccount.keyTypeMuxedEd25519( new types.MuxedAccountMed25519({ id: types.Uint64.fromString(id), ed25519: this._publicKey }) ); } return types.MuxedAccount.keyTypeEd25519(this._publicKey); } /** * Returns raw public key bytes */ rawPublicKey() { return this._publicKey; } /** * Returns the signature hint for this keypair. * The hint is the last 4 bytes of the account ID XDR representation. */ signatureHint() { const a = this.xdrAccountId().toXDR(); return a.subarray(a.length - 4); } /** * Returns public key associated with this `Keypair` object. */ publicKey() { return StrKey.encodeEd25519PublicKey(this._publicKey); } /** * Returns secret key associated with this `Keypair` object. * * The secret key is encoded in Stellar format (e.g., `SDAK....`). * * @throws if no secret key is available */ secret() { if (!this._secretSeed) { throw new Error("no secret key available"); } if (this.type === "ed25519") { return StrKey.encodeEd25519SecretSeed(this._secretSeed); } throw new Error("Invalid Keypair type"); } /** * Returns raw secret key bytes. * * @throws if no secret seed is available */ rawSecretKey() { if (!this._secretSeed) { throw new Error("no secret seed available"); } return this._secretSeed; } /** * Returns `true` if this `Keypair` object contains secret key and can sign. */ canSign() { return !!this._secretKey; } /** * Signs data. * * @param data - data to sign * @throws if no secret key is available */ sign(data) { if (!this._secretKey) { throw new Error("cannot sign: no secret key available"); } return sign(data, this._secretKey); } /** * Verifies if `signature` for `data` is valid. * * @param data - signed data * @param signature - signature to verify */ verify(data, signature) { try { return verify(data, signature, this._publicKey); } catch { return false; } } /** * Signs an arbitrary message per SEP-53. * * The message is UTF-8 encoded (if a string), prefixed with the fixed * `"Stellar Signed Message:\n"` marker, hashed with SHA-256, and that hash is * signed with this keypair's ed25519 secret key. * * @param message - the message to sign (a UTF-8 string or raw bytes) * @returns the 64-byte ed25519 signature * @throws if no secret key is available * @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md */ signMessage(message) { return this.sign(this._hashMessage(message)); } /** * Verifies a SEP-53 signed message against this keypair's public key. * * @param message - the original message (a UTF-8 string or raw bytes) * @param signature - the 64-byte signature to verify * @returns `true` if `signature` is valid for `message` and this key * @see https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0053.md */ verifyMessage(message, signature) { try { return this.verify(this._hashMessage(message), signature); } catch { return false; } } /** * Computes the SEP-53 message hash: * `SHA-256("Stellar Signed Message:\n" + message)`. */ _hashMessage(message) { const messageBytes = typeof message === "string" ? Buffer.from(message, "utf8") : message; return hash(Buffer.concat([MESSAGE_PREFIX, messageBytes])); } /** * Returns the decorated signature (hint+sig) for arbitrary data. * * The returned structure can be added directly to a transaction envelope. * * @param data - arbitrary data to sign * * @see TransactionBase.addDecoratedSignature */ signDecorated(data) { const signature = this.sign(data); const hint = this.signatureHint(); return new types.DecoratedSignature({ hint, signature }); } /** * Returns the raw decorated signature (hint+sig) for a signed payload signer. * * The hint is defined as the last 4 bytes of the signer key XORed with last * 4 bytes of the payload (zero-left-padded if necessary). * * @param data - data to both sign and treat as the payload * * @see https://github.com/stellar/stellar-protocol/blob/master/core/cap-0040.md#signature-hint * @see TransactionBase.addDecoratedSignature */ signPayloadDecorated(data) { const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data); const signature = this.sign(dataBuffer); const keyHint = this.signatureHint(); let hint = Buffer.from(dataBuffer.subarray(-4)); if (hint.length < 4) { hint = Buffer.concat([hint, Buffer.alloc(4 - hint.length, 0)]); } for (let i = 0; i < hint.length; i++) { hint[i] = hint[i] ^ keyHint[i]; } return new types.DecoratedSignature({ hint, signature }); } } export { Keypair }; //# sourceMappingURL=keypair.js.map