ECDSA Overview and Examples

🚧

Use the Code Examples!

When you start building your own app, we strongly recommend using the code examples for authentication in this section to ensure your messages are being properly signed.

Where many other API systems may require client IDs and secrets, Sila uses the Elliptic Curve Digital Signature Algorithm (ECDSA) to secure and validate requests.

Apps and users will have private keys, and Sila will only store addresses, which are directly derived from public keys. Since private keys cannot be derived from addresses or public keys, this can be considered a zero knowledge proof.

  

Process Overview

  1. A private/public ECDSA key pair is generated.
  2. An "address" is derived from the public key.
  3. The address is registered to a "handle" (like usernames in the Sila ecosystem) and stored by Sila.
  4. A JSON request body is constructed and stringified. (This is the "message.")
  5. A hash is generated from the message (using SHA3/Keccak-256).
  6. The private key is used to sign the hash, which produces a signature.
  7. A valid request to Sila can now be sent. The original "message" is sent in the POST request body, and a hex-encoded signature string is sent in a request header.
  8. When a Sila endpoint receives the request, it will derive a public key from the message and the signature. An address is then derived from that public key.
  9. If the derived address is registered under the handle that is specified in the request body, the request is validated.
  

Code Examples

Pick a language tab for a signature generation example.

1. Import a library for hashing and signing.
2. Get appropriate private key to use in signature.
3. Marshal request body to JSON, producing a "message."
    * If just testing signatures locally, you can use an example string.
4. Hash message with the Keccak-256 algorithm.
5. Sign the hash with the private key, producing a "signature."
6. Check and adjust results with further steps if needed.
    * Remove 0x prefix from signature hex string if present.
    * Check for an offset issue (last few characters will differ from expected results).
    * If there is an offset issue:
        - Do hex arithmetic to figure out the difference between expected and actual results.
        - When producing signatures, add the discovered offset.
        - Make sure corrected signature strings are 130 characters long.
// JavaScript authentication example:

// For this example, we will use a library for message signing.
// See: https://github.com/pubkey/eth-crypto
const EthCrypto = require('eth-crypto');

// You will need a message (request body contents) and a private key.
// This private key will be an Ethereum private key, hex-encoded 
// and 64 characters in length, omitting any "0x" that might precede it.
var private_key = 'badba7368...c0202a97c';
var messageJSON = { test: 'message' };

// Stringify the message. (The string that gets hashed should be
// guaranteed to be the same as what is sent in the request.)
// NOTE: if testing the example strings, you can just declare them as
// strings, e.g. var message = 'Sila';
var message = JSON.stringify(messageJSON);

// Generate the message hash using the Keccak 256 algorithm.
var msg_hash = EthCrypto.hash.keccak256(message);

// Create a signature using your private key and the hashed message.
var signature = EthCrypto.sign(private_key, messageHash);

// The EthCrypto library adds a leading '0x' which should be removed 
// from the signature.
signature = signature.substring(2);

// The raw message should then be sent in an HTTP request body, and the signature
// should be sent in a header.
var request_data = {};
request_data.headers.signature = signature;
request_data.body = message;

//...
# Python authentication example:

# For this example, we will use a library for message signing.
# See: https://github.com/ethereum/eth-account
from eth_account import Account
import sha3
import json

# You will need a message (request body contents) and a private key.
# This private key will be an Ethereum private key, hex-encoded 
# and 64 characters in length, omitting any "0x" that might precede it.
key = 'badba7368...c0202a97c'
msg = {
    'test': 'message'
}

# Stringify the message. (The string that gets hashed should be
# guaranteed to be the same as what is sent in the request.)
# NOTE: if testing the example strings, you can just declare them as strings.
encoded_message = (json.dumps(msg)).encode("utf-8")

# Generate the message hash using the Keccak 256 algorithm.
k = sha3.keccak_256()
k.update(encoded_message)
message_hash = k.hexdigest()

# Sign the message_hash.
signed_message = Account.signHash(message_hash, key)
sig_hx = signed_message.signature.hex()

# replace 0x with empty string
signature = str(sig_hx.replace("0x","")))

# Use encoded_message in the request body and sig_hx
# in the appropriate signature header.
print(signature)
print(encoded_message)

#...

# You can also sign messages with the Sila-Python SDK.

from silasdk import EthWallet

EthWallet.signMessage("my_message", "private_key")
// Go authentication example:

package main

import (
    "encoding/hex"
    "encoding/json"
    "log"
    "math/big"

    // For this example, we will use a library for message signing.
    // See: https://github.com/ethereum/go-ethereum/crypto
    "github.com/ethereum/go-ethereum/crypto"
)

func main() {
    // You will need a message (the contents of the request body
    // you will send) and a private key.
    // This private key will be an ethereum private key. In this 
    // example, it should be a hex string 64 characters in length,
    // omitting any "0x" that might precede the number.
    privateKey := "badba7368...c0202a97c"
    messageJSON := map[string]interface{}{
        "test": "message",
    }

    // Convert a private key from hex to the *ecdsa.PrivateKey
    // type in your function if needed.
    pk, err := crypto.HexToECDSA(privateKey)
    if err != nil {
        log.Fatal(err)
    }

    // Marshal the message to JSON; this function returns bytes.
    // (The bytes that get hashed should be guaranteed to be the
    // same as what is sent in the request.)
    // NOTE: if testing the example strings, you can just declare them as
    // strings and cast them to bytes, e.g. message := []byte("Sila")
    message, err := json.Marshal(&messageJSON)
    if err != nil {
        log.Fatal(err)
    }

    // Generate the message hash using the Keccak 256 algorithm.
    msgHash := crypto.Keccak256(message)

    // Create a signature using your private key and hashed message.
    sigBytes, err := crypto.Sign(msgHash, pk)
    if err != nil {
        log.Fatal(err)
    }

    // The signature just created is off by -27 from what the API
    // will expect. Correct that by converting the signature bytes 
    // to a big int and adding 27.
    var offset int64 = 27
    var bigSig = new(big.Int).SetBytes(sigBytes)
    sigBytes = bigSig.Add(bigSig, big.NewInt(offset)).Bytes()

    // The big library takes out any padding, but the resultant 
    // signature must be 130 characters (65 bytes) long. In some 
    // cases, you might find that sigBytes now has a length of 64 or
    // less, so you can fix that in this way (this prepends the hex 
    // value with "0" until the requisite length is reached).
    // Example: if two digits were required but the value was 1, you'd 
    // pass in 01.
    var sigBytesLength = 65 // length of a valid signature byte array
    var arr = make([]byte, sigBytesLength)
    copy(arr[(sigBytesLength-len(sigBytes)):], sigBytes)

    // Encode the bytes to a hex string.
    sig := hex.EncodeToString(arr)

    // The raw message should then be sent in an HTTP request body, and
    // the signature should be sent in a header.
    log.Println("Message:", string(message))
    log.Println("Signature:", sig, "Signature length:", len(sig))