A TypeScript implementation of the yral-identity library for authenticating with Yral off-chain services. This library provides a compatible way to sign messages with Internet Computer identities and produce signatures that are compatible with the Rust-based yral-identity library.
import { Ed25519KeyIdentity } from "@dfinity/identity";
import { Principal } from "@dfinity/principal";
import { IDL } from "@dfinity/candid";
import { Message, signMessage } from "yral-identity-ts";
// Create or load an identity
const identity = Ed25519KeyIdentity.generate();
// Create a message
const canisterId = Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai");
const message = new Message()
.withCanisterId(canisterId)
.withMethodName("greet")
.withArgs([IDL.Text], ["Hello, World!"])
.withIngressMaxAge(300_000);
// Sign the message
const signature = await signMessage(identity, message);
// Convert to JSON for sending to the service
const signatureJson = signature.serializeJSON();
The library supports delegations, which allow one identity to act on behalf of another:
import {
DelegationChain,
DelegationIdentity,
Ed25519KeyIdentity,
} from "@dfinity/identity";
import { Principal } from "@dfinity/principal";
import { Message, signMessage } from "yral-identity-ts";
import { IDL } from "@dfinity/candid";
// Create identities
const identity = Ed25519KeyIdentity.generate();
const delegationIdentity = Ed25519KeyIdentity.generate();
// Create delegation chain
const delegation = await DelegationChain.create(
identity,
delegationIdentity.getPublicKey(),
);
// Create delegation identity
const delegatedIdentity = DelegationIdentity.fromDelegation(
delegationIdentity,
delegation,
);
// Create and sign a message with the delegated identity
const message = new Message()
.withCanisterId(Principal.fromText("rrkah-fqaaa-aaaaa-aaaaq-cai"))
.withMethodName("greet")
.withArgs([IDL.Text], ["Hello, World!"])
.withIngressMaxAge(300_000);
const signature = await signMessage(delegatedIdentity, message);
// This signature with delegations can now be serialized
const signatureJson = signature.serializeJSON();
The library includes tests that verify:
- Basic functionality with Ed25519KeyIdentity
- JSON serialization compatibility with the Rust implementation
- Proper Candid encoding of arguments
- Delegation support and serialization
Run the tests with:
npm test
Error class for yral-identity-ts operations.
class YralIdentityError extends Error {
static SenderNotFound: YralIdentityError;
static Signing(e: Error): YralIdentityError;
static ArgumentEncoding(e: Error): YralIdentityError;
}
The Message
class represents a message that can be signed and sent to a canister.
constructor(
canisterId: Principal = Principal.anonymous(),
methodName: string = '',
args: Uint8Array = new Uint8Array(),
sender: Principal = Principal.anonymous(),
ingressExpiry: number = 120_000,
nonce?: Uint8Array
)
-
canisterId: Principal
- The ID of the canister that will receive the message -
methodName: string
- The name of the method to call -
args: Uint8Array
- The encoded Candid arguments -
sender: Principal
- The Principal of the message sender -
ingressExpiry: Expiry
- When the message expires -
nonce?: Uint8Array
- Optional nonce for replay protection -
ingressMaxAgeMs: number
- The maximum age of the message in milliseconds
-
withCanisterId(canisterId: Principal): Message
- Sets the canister ID -
withMethodName(methodName: string): Message
- Sets the method name -
withArgs(argTypes: IDL.Type[], args: unknown[]): Message
- Sets and encodes the arguments as Candid -
withIngressMaxAge(maxAgeMs: number): Message
- Sets how long the message is valid -
withNonce(nonce: Uint8Array): Message
- Sets a nonce for the message -
toCallRequest(): CallRequest
- Converts to a CallRequest for signing
interface Delegation {
pubkey: Uint8Array; // Public key of the delegatee
expiration_ns: bigint; // Expiration in nanoseconds since epoch
targets?: Principal[]; // Optional target canisters for the delegation
}
interface SignedDelegation {
delegation: Delegation; // The delegation details
signature: Uint8Array; // Signature from the delegator
}
The Signature
class represents a signature that can be sent to a service for authentication.
constructor(
sig?: Uint8Array,
publicKey?: Uint8Array,
ingressExpiry: Expiry = new Expiry(120_000),
sender: Principal = Principal.anonymous(),
delegations?: SignedDelegation[]
)
-
sig?: Uint8Array
- The signature bytes -
public_key?: Uint8Array
- The public key bytes of the signer -
ingress_expiry: Expiry
- When the signature expires -
delegations?: SignedDelegation[]
- Optional delegations for delegation signing -
sender: Principal
- The Principal of the signer
-
static fromAgentSignature(signature: AgentSignature, ingressExpiry: Expiry, sender: Principal): Signature
- Converts from agent signature format -
serializeJSON(): string
- Converts the signature to a JSON string suitable for interoperability with Rust services
async function signMessage(
identity: DelegationIdentity | SignIdentity,
message: Message,
): Promise<Signature>;
Signs a message using the provided identity. This function:
- Sets the sender in the message to the identity's principal
- Creates a CallRequest from the message
- Calculates the request ID
- Signs the request ID using the provided identity
- Extracts the signature and public key
- Creates and returns a Signature object
The Signature
class includes a serializeJSON()
method that ensures proper serialization of all properties including Uint8Array
objects, which are converted to standard arrays. This is crucial for compatibility with the Rust implementation.
When using the serializeJSON()
method on a Signature
instance, the result will be properly formatted for use with yral-identity Rust services:
{
"sig": [1, 2, 3, ...],
"public_key": [4, 5, 6, ...],
"ingress_expiry": {
"secs": 120,
"nanos": 0
},
"delegations": [
{
"delegation": {
"pubkey": [7, 8, 9, ...],
"expiration_ns": 3600000000000,
"targets": ["aaaaa-aa"]
},
"signature": [10, 11, 12, ...]
}
],
"sender": "rrkah-fqaaa-aaaaa-aaaaq-cai"
}
MIT