VerifySignature
Validate Transaction Signatures#
The VerifySignature method verifies that a cryptographic signature is valid for given transaction data and public key. This is essential for security validation, multi-signature workflows, and ensuring transaction authenticity before execution.
Method Signature#
Service: sui.rpc.v2beta2.SignatureVerificationService
Method: VerifySignature
Type: Unary RPC
Parameters#
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction | bytes | Yes | BCS-encoded transaction bytes |
signature | bytes | Yes | Signature bytes |
public_key | bytes | Yes | Public key bytes |
scheme | SignatureScheme | Yes | Signature scheme (ED25519, SECP256K1, etc.) |
Signature Schemes#
| Scheme | Value | Description |
|---|---|---|
| ED25519 | 0 | Ed25519 signature scheme |
| SECP256K1 | 1 | Secp256k1 (Ethereum-compatible) |
| SECP256R1 | 2 | Secp256r1 (P-256) |
| MULTISIG | 3 | Multi-signature |
Response Structure#
message VerifySignatureResponse {
bool is_valid = 1;
string error_message = 2;
}
Code Examples#
- TypeScript
- Python
- Go
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
const ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com';
const API_TOKEN = 'your_api_token_here';
const packageDefinition = protoLoader.loadSync('./protos/signature.proto', {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
includeDirs: ['./protos']
});
const protoDescriptor = grpc.loadPackageDefinition(packageDefinition) as any;
const credentials = grpc.credentials.createSsl();
const client = new protoDescriptor.sui.rpc.v2beta2.SignatureVerificationService(
ENDPOINT,
credentials
);
const metadata = new grpc.Metadata();
metadata.add('x-api-key', API_TOKEN);
enum SignatureScheme {
ED25519 = 0,
SECP256K1 = 1,
SECP256R1 = 2,
MULTISIG = 3
}
async function verifySignature(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
scheme: SignatureScheme
): Promise<{ isValid: boolean; error?: string }> {
return new Promise((resolve, reject) => {
const request = {
transaction: transactionBytes,
signature: signature,
public_key: publicKey,
scheme: scheme
};
client.VerifySignature(request, metadata, (error: any, response: any) => {
if (error) {
console.error('VerifySignature error:', error.message);
reject(error);
return;
}
resolve({
isValid: response.is_valid,
error: response.error_message
});
});
});
}
// Usage
const result = await verifySignature(
txBytes,
signatureBytes,
publicKeyBytes,
SignatureScheme.ED25519
);
if (result.isValid) {
console.log('Signature is valid');
} else {
console.error('Signature verification failed:', result.error);
}
import grpc
import signature_service_pb2
import signature_service_pb2_grpc
ENDPOINT = 'api-sui-mainnet-full.n.dwellir.com'
API_TOKEN = 'your_api_token_here'
def verify_signature(
transaction_bytes: bytes,
signature: bytes,
public_key: bytes,
scheme: int
):
credentials = grpc.ssl_channel_credentials()
channel = grpc.secure_channel(ENDPOINT, credentials)
client = signature_service_pb2_grpc.SignatureVerificationServiceStub(channel)
request = signature_service_pb2.VerifySignatureRequest(
transaction=transaction_bytes,
signature=signature,
public_key=public_key,
scheme=scheme
)
metadata = [('x-api-key', API_TOKEN)]
response = client.VerifySignature(request, metadata=metadata)
if response.is_valid:
print('Signature is valid')
else:
print(f'Signature verification failed: {response.error_message}')
channel.close()
return response
# Usage
# ED25519 = 0
result = verify_signature(tx_bytes, sig_bytes, pub_key_bytes, 0)
package main
import (
"context"
"fmt"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
pb "your-module/gen/sui/rpc/v2beta2"
)
const (
endpoint = "api-sui-mainnet-full.n.dwellir.com"
apiToken = "your_api_token_here"
)
func verifySignature(
txBytes []byte,
signature []byte,
publicKey []byte,
scheme pb.SignatureScheme,
) (*pb.VerifySignatureResponse, error) {
creds := credentials.NewClientTLSFromCert(nil, "")
conn, err := grpc.Dial(endpoint, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, err
}
defer conn.Close()
client := pb.NewSignatureVerificationServiceClient(conn)
ctx := metadata.AppendToOutgoingContext(
context.Background(),
"x-api-key", apiToken,
)
req := &pb.VerifySignatureRequest{
Transaction: txBytes,
Signature: signature,
PublicKey: publicKey,
Scheme: scheme,
}
resp, err := client.VerifySignature(ctx, req)
if err != nil {
return nil, err
}
if resp.IsValid {
fmt.Println("Signature is valid")
} else {
fmt.Printf("Signature verification failed: %s\n", resp.ErrorMessage)
}
return resp, nil
}
func main() {
result, err := verifySignature(
txBytes,
signatureBytes,
publicKeyBytes,
pb.SignatureScheme_ED25519,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Valid: %t\n", result.IsValid)
}
Use Cases#
Pre-Execution Validation#
async function validateBeforeExecution(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
scheme: SignatureScheme
): Promise<boolean> {
const verification = await verifySignature(
transactionBytes,
signature,
publicKey,
scheme
);
if (!verification.isValid) {
console.error('Cannot execute: Invalid signature');
console.error('Reason:', verification.error);
return false;
}
console.log('Signature verified, safe to execute');
return true;
}
// Usage
const canExecute = await validateBeforeExecution(
txBytes,
signatureBytes,
publicKeyBytes,
SignatureScheme.ED25519
);
if (canExecute) {
await executeTransaction(txBytes, signatureBytes, publicKeyBytes);
}
Multi-Signature Verification#
interface SignerInfo {
publicKey: Uint8Array;
signature: Uint8Array;
scheme: SignatureScheme;
}
async function verifyMultiSig(
transactionBytes: Uint8Array,
signers: SignerInfo[],
threshold: number
): Promise<{
isValid: boolean;
validSignatures: number;
invalidSigners: number[];
}> {
const verificationPromises = signers.map((signer, index) =>
verifySignature(
transactionBytes,
signer.signature,
signer.publicKey,
signer.scheme
)
.then(result => ({ index, ...result }))
.catch(() => ({ index, isValid: false, error: 'Verification failed' }))
);
const results = await Promise.all(verificationPromises);
const validSignatures = results.filter(r => r.isValid).length;
const invalidSigners = results.filter(r => !r.isValid).map(r => r.index);
return {
isValid: validSignatures >= threshold,
validSignatures,
invalidSigners
};
}
// Usage - 2 of 3 multisig
const signers: SignerInfo[] = [
{ publicKey: pk1, signature: sig1, scheme: SignatureScheme.ED25519 },
{ publicKey: pk2, signature: sig2, scheme: SignatureScheme.ED25519 },
{ publicKey: pk3, signature: sig3, scheme: SignatureScheme.ED25519 }
];
const multiSigResult = await verifyMultiSig(txBytes, signers, 2);
if (multiSigResult.isValid) {
console.log(`Valid: ${multiSigResult.validSignatures}/3 signatures`);
} else {
console.error('Insufficient valid signatures');
console.error('Invalid signers:', multiSigResult.invalidSigners);
}
Signature Scheme Detection#
async function detectAndVerifySignature(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array
): Promise<{ scheme: SignatureScheme; isValid: boolean } | null> {
const schemes = [
SignatureScheme.ED25519,
SignatureScheme.SECP256K1,
SignatureScheme.SECP256R1
];
for (const scheme of schemes) {
try {
const result = await verifySignature(
transactionBytes,
signature,
publicKey,
scheme
);
if (result.isValid) {
return { scheme, isValid: true };
}
} catch (error) {
// Try next scheme
continue;
}
}
return null;
}
// Usage
const detection = await detectAndVerifySignature(txBytes, sig, pubKey);
if (detection) {
console.log(`Valid signature using ${SignatureScheme[detection.scheme]}`);
} else {
console.error('No valid signature scheme found');
}
Transaction Replay Protection#
class SignatureValidator {
private verifiedSignatures = new Set<string>();
async verifyOnce(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
scheme: SignatureScheme
): Promise<boolean> {
// Create unique key for this transaction+signature
const key = this.createSignatureKey(transactionBytes, signature);
if (this.verifiedSignatures.has(key)) {
console.warn('Signature already used (replay attempt detected)');
return false;
}
const result = await verifySignature(
transactionBytes,
signature,
publicKey,
scheme
);
if (result.isValid) {
this.verifiedSignatures.add(key);
return true;
}
return false;
}
private createSignatureKey(tx: Uint8Array, sig: Uint8Array): string {
// Simple concatenation - use proper hash in production
return `${Array.from(tx).join(',')}_${Array.from(sig).join(',')}`;
}
clear(): void {
this.verifiedSignatures.clear();
}
}
// Usage
const validator = new SignatureValidator();
const isValid = await validator.verifyOnce(txBytes, sig, pubKey, scheme);
const isDuplicate = await validator.verifyOnce(txBytes, sig, pubKey, scheme);
console.log('First verification:', isValid);
console.log('Second verification (replay):', isDuplicate); // false
Batch Signature Verification#
interface BatchVerificationItem {
transactionBytes: Uint8Array;
signature: Uint8Array;
publicKey: Uint8Array;
scheme: SignatureScheme;
id: string;
}
async function batchVerifySignatures(
items: BatchVerificationItem[]
): Promise<Map<string, boolean>> {
const results = new Map<string, boolean>();
const verifications = items.map(async item => {
try {
const result = await verifySignature(
item.transactionBytes,
item.signature,
item.publicKey,
item.scheme
);
results.set(item.id, result.isValid);
} catch (error) {
results.set(item.id, false);
}
});
await Promise.all(verifications);
return results;
}
// Usage
const batch: BatchVerificationItem[] = [
{ id: 'tx1', transactionBytes: tx1, signature: sig1, publicKey: pk1, scheme: SignatureScheme.ED25519 },
{ id: 'tx2', transactionBytes: tx2, signature: sig2, publicKey: pk2, scheme: SignatureScheme.ED25519 },
{ id: 'tx3', transactionBytes: tx3, signature: sig3, publicKey: pk3, scheme: SignatureScheme.SECP256K1 }
];
const results = await batchVerifySignatures(batch);
for (const [id, isValid] of results) {
console.log(`${id}: ${isValid ? 'Valid' : 'Invalid'}`);
}
Best Practices#
Always Verify Before Execute#
async function safeExecuteTransaction(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
scheme: SignatureScheme
): Promise<any> {
// Step 1: Verify signature
const verification = await verifySignature(
transactionBytes,
signature,
publicKey,
scheme
);
if (!verification.isValid) {
throw new Error(`Invalid signature: ${verification.error}`);
}
// Step 2: Execute only if valid
return await executeTransaction(transactionBytes, signature, publicKey);
}
Handle Verification Errors Gracefully#
async function verifySignatureSafe(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
scheme: SignatureScheme
): Promise<{ isValid: boolean; error?: string }> {
try {
return await verifySignature(transactionBytes, signature, publicKey, scheme);
} catch (error: any) {
console.error('Signature verification failed:', error.message);
return {
isValid: false,
error: error.message
};
}
}
Cache Verification Results#
class VerificationCache {
private cache = new Map<string, { isValid: boolean; timestamp: number }>();
private ttl = 60000; // 1 minute
async verify(
transactionBytes: Uint8Array,
signature: Uint8Array,
publicKey: Uint8Array,
scheme: SignatureScheme
): Promise<boolean> {
const key = this.createKey(transactionBytes, signature, publicKey);
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.isValid;
}
const result = await verifySignature(
transactionBytes,
signature,
publicKey,
scheme
);
this.cache.set(key, {
isValid: result.isValid,
timestamp: Date.now()
});
return result.isValid;
}
private createKey(tx: Uint8Array, sig: Uint8Array, pk: Uint8Array): string {
return `${tx.length}_${sig.length}_${pk.length}`;
}
}
Performance Characteristics#
| Metric | Value |
|---|---|
| Typical Latency | 10-30ms |
| Response Size | Less than 100 bytes |
| Cache Recommended | Yes (short TTL) |
| Rate Limit Impact | Low |
Signature Scheme Comparison#
| Scheme | Security | Performance | Use Case |
|---|---|---|---|
| ED25519 | High | Fast | General purpose, recommended |
| SECP256K1 | High | Moderate | Ethereum compatibility |
| SECP256R1 | High | Moderate | WebAuthn, mobile devices |
| MULTISIG | High | Slower | Multi-party approval |
Common Errors#
| Error Code | Scenario | Solution |
|---|---|---|
INVALID_ARGUMENT | Malformed signature or key | Verify byte encoding |
FAILED_PRECONDITION | Invalid scheme for key type | Match scheme to key type |
UNAUTHENTICATED | Missing/invalid token | Verify x-api-key header |
Related Methods#
- ExecuteTransaction - Execute verified transactions
- SimulateTransaction - Test transactions before signing
Need help? Contact support@dwellir.com or check the gRPC overview.