Search code examples
.netethereumpublic-keyazure-keyvaultjwk

Obtain public key byte array from Json Web Key


I have a Json Web Key that contains public key information and I'd like to get the public key as byte array. My goal is to eventually derive ethereum address from it, using Keccak hash. I understand the process of getting the address from the public key byte array, but I don't know how to obtain this byte array. My scenario is, generate EC key using Azure Key Vault API, retrieve the key (JWK format) and then find the address. The JWK has the following format.

{
  "kid": "https: //mykeyvault.vault.azure.net/keys/testeckey/8bad08aaae514efe981eaab4e590778d",
  "kty": "EC",
  "key_ops": [
    "sign",
    "verify"
  ],
  "crv": "P-256",
  "x": "YooqHyo7hlmcrBs5lDSSUsB0axzvorjxzNl6DBZLUf0",
  "y": "NM-JrV6NTbUgILY_sBm5VgYxt1zYccCOCFtSDicSfWM"
}

I'm using the Azure .NET SDK and I have the option to use Bouncy Castle for .NET or any JS library, since project can also run in a Node.js environment. How can I obtain the byte array of the public key? Thank you


Solution

  • Answering my own question in case anyone else needs this. I also wrote an article about this, hope someone finds it useful.

    https://tmarkovski.github.io/eth-azure/keyvault-part1/

    Initially, I was generating the incorrect key type. I was able to generate the key by concatenating the X and Y arrays from JWK. Here's my sample code I was playing with in F#. It uses BouncyCastle for the Keccak hash function. Important thing to note is the use of "EC-HSM" key type. This is part of the Premium SKU for Key Vault and only this type supports SECP256K1 curve, otherwise throws an exception if using "EC" key type.

    open Microsoft.Azure.KeyVault
    open System.Threading.Tasks
    open System
    open Microsoft.IdentityModel.Clients.ActiveDirectory
    open Microsoft.Azure.KeyVault.Models
    open Microsoft.Azure.KeyVault.WebKey
    open Org.BouncyCastle.Crypto.Digests
    open Org.BouncyCastle.Crypto
    
    /// Implements an extension method that overloads the standard
    /// 'Bind' of the 'async' builder. The new overload awaits on 
    /// a standard .NET task
    type AsyncBuilder with
      member __.Bind(t:Task<'T>, f:'T -> Async<'R>) : Async<'R>  = 
        async.Bind(Async.AwaitTask t, f)
    
    let vaultUri = "https://__mykeyvault__.vault.azure.net/"
    let clientId = "..."
    let clientSecret = "..."
    
    type AuthenticationCallback = KeyVaultClient.AuthenticationCallback
    
    let getAccessToken (authority:string) (resource:string) (scope:string) =    
        let clientCredential = new ClientCredential(clientId, clientSecret)
        let context = new AuthenticationContext(authority, TokenCache.DefaultShared)
        async {
            let! result = context.AcquireTokenAsync(resource, clientCredential)
            return result.AccessToken;
        } |> Async.StartAsTask
    
    let client = new KeyVaultClient(new KeyVaultCredential(new AuthenticationCallback(getAccessToken)))
    
    let createKey name =
        let keyParams = new NewKeyParameters()
        keyParams.Kty <- "EC-HSM"
        keyParams.CurveName <- EcKey.SECP256K1
    
        async {
            let! result = client.CreateKeyAsync(vaultUri, name, keyParams)
            return result
        } |> Async.RunSynchronously
    
    let getKey name = 
        async {
            let! result = client.GetKeyAsync(vaultBaseUrl = vaultUri, keyName = name)
            return result
        } |> Async.RunSynchronously
    
    let getPubKey (jwk:KeyBundle) = 
         Array.concat [| jwk.Key.X; jwk.Key.Y |]
    
    let hash (digest:IDigest) data =
        digest.Reset()
        digest.BlockUpdate(data, 0, data.Length)
    
        let a = digest.GetDigestSize() |> Array.zeroCreate
        digest.DoFinal(a, 0) |> ignore
        a
    
    let toHex (x:byte) = x.ToString("x2")
    
    let getAddress pubKey =
        pubKey
        |> hash (new KeccakDigest(256))
        |> Array.map toHex
        |> Array.skip 12
        |> String.Concat
        |> (+) "0x"
    
    
    createKey "testEcKey"
    |> getPubKey
    |> getAddress
    |> Console.WriteLine
    
    // 0x51e4370152c132d307c302d8146c63ca6bf41167