Changing A Wallet Policy Tutorial

What is a Policy

Each wallet has an associated policy that dictates the private/public keys which are allowed to spend from it. This key is called the Instruction Key and usually is kept in the secure enclave of the iPhone device that you onboarded with. However API users can change the Instruction Key so they sign transactions without the phone.

This tutorial is for users who:

  • Wish to register their new Instruction Key (i.e. want to remove the iOS device from the signing process and manage their Instruction Key).
  • Already manage their own Instruction Key and wish to rotate it.

For iPhone users changing phones then the recovery process is done via our app and the following tutorial is not relevant.

Brief outline of the process

Note: This process only needs to be done once for every wallet owned. (Although you can create a single external key for use across ALL your wallets)

  1. Create a new public/private key pair on the correct curve as your signing key. (All transactions in future will be signed with this key)
    • For Production you MUST use a secure key storage solution (e.g. AWS KMS)
  2. Create a new wallet policy for your wallet that includes your new Instruction Key (Remember: ALL wallet policy delegates MUST sign (as well as Bitpanda Custody) before the new wallet policy can be used)
    1. Creating a new wallet policy can be done in few different ways
      • Create the policy change via the SDK and use the sign call back to sign with your new external key
        • OR
      • Create the policy change via the GraphQL API and then call the AddSignature Mutation API endpoint to sign with your new external key
        • OR
      • Register your site to receive the POLICY_CHANGE_REQUEST_CREATED webhook (Ask Bitpanda Custody to set this up) and then ask Bitpanda Custody to create the policy change, wait for the webhook, and call the AddSignature Mutation API endpoint to sign with your new external key
  3. Which ever wallet policy change method is chosen, you will need to verify the response obtained from the policy change request is valid
    1. For testing purposes you can skip this step
    2. For Production we highly recommend you verify the data
  4. Sign the data. This confirms you have access to the private key just created and that you agree to the new wallet policy.
    • If your signing solution requires the pre-image data then use the unverifiedDigestData.signData and use the SHA256 hashing algorithm
    • If your signing solution can sign hash data then use the unverifiedDigestData.shaSignData
  5. Submit the public key and signature by calling the AddSignature Mutation mutation
  6. Repeat this for all wallets that need to be updated with the new Instruction Key

Creating the public/private key

  1. The new keypair MUST be on on the secp256r1 (also known as P-256 and prime256v1) curve. Please note this is the “r” curve (sometimes called the “p” curve) which is different to the “k” curve that Bitcoin or Ethereum uses.
    • if you use AWS KMS you should use the following paramters:
      • KeyType: Asymmetric
      • KeySpec: ECC_NIST_P256
      • Signing Algorithm: ECDSA_SHA_256
  2. To validate the curve you can use the SDK or the following tool to check (https://report-uri.com/home/pem_decoder) by uploading the public key PEM file. e.g.
    1
    2
    3
    4
    -----BEGIN PUBLIC KEY-----
    MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEI8zNvjURIX2LVEQn49icMqDaydvX
    5ZLRxsL4M33gKAcZ4Nm4VlziXyyG2ddHCZ3vmp7UYtZGcr8Xa/8c4wuyYg==
    -----END PUBLIC KEY-----

Gives the result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Array
(
[bits] => 256
[key] => -----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEI8zNvjURIX2LVEQn49icMqDaydvX
5ZLRxsL4M33gKAcZ4Nm4VlziXyyG2ddHCZ3vmp7UYtZGcr8Xa/8c4wuyYg==
-----END PUBLIC KEY-----
[ec] => Array
(
[curve_name] => prime256v1
[curve_oid] => 1.2.840.10045.3.1.7
[x] => 23cccdbe3511217d8b544427e3d89c32a0dac9dbd7e592d1c6c2f8337de02807
[y] => 19e0d9b8565ce25f2c86d9d747099def9a9ed462d64672bf176bff1ce30bb262
)

[type] => 3
)

The prime256v1 confirms this is on the correct curve.
The x value and the y value can simply be concatenated with 04 at the front to produce the publicKey in the format TrustVault requires.

i.e:

1
0423cccdbe3511217d8b544427e3d89c32a0dac9dbd7e592d1c6c2f8337de0280719e0d9b8565ce25f2c86d9d747099def9a9ed462d64672bf176bff1ce30bb262

Creating the new Wallet Policy and Signing it

Option 1 - Typescript (Javascript) example (used in conjunction with the SDK signed callback as a reference implementation)

The NodesJS SDK provides a reference implementation of how to use the GraphQL APIs and provides numerous helper methods such as webhook validation or DER encoding. (DER encoding is used for digest validation).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// Construct the SDK
const trustVault = new TrustVault({
environment: "sandbox",
apiKey: "<APIKEY>",
});

// Some class to perform your key signing operations
class keyPair{

// this would wrap your keystore library. e.g. this would call AWS KMS. See sample code here: https://github.com/Trustology/trustvault-nodejs-sdk/blob/master/src/ts/sign-examples/aws-kms.ts
public sign() {
// call AWS

}
}

// This sign call back. This will sign the data returned from the callback. The callback is returned once the policy change request has been successfully created and will automatically, add the signature data to the request and send it to TrustVault
const sign: SignCallback = async ({ shaSignData }: SignDataBuffer): Promise<PublicKeySignaturePairBuffer> => {
// validate the data returned before signing it
// myCustomValidationMethod()

// using you private key pair, sign the digest returned from the server
const { r, s } = keyPair.sign(shaSignData);
// convert the r, s bytes signature to hex format
const hexSignature = r.toString("hex", 64) + s.toString("hex", 64);

const publicKeySignaturePair: PublicKeySignaturePairBuffer = {
publicKey: Buffer.from(publicKey, "hex"),
signature: Buffer.from(hexSignature, "hex"),
};
console.log("publicKeySignaturePair: ", publicKeySignaturePair);

return publicKeySignaturePair;
};

// function to create the new wallet policy
const changeWalletPolicy = async (walletId: string, publicKey: string) => {
// The SDK method (replacePublicKeyInDefaultSchedule) will create a new wallet policy request
// and will create a default policy that just includes the publicKey. This is the simplest
// policy that only requires a signature from this publicKey to spend funds. Other, more
// complex policy schedules, can be added if required by using the SDK (createWalletPolicyChangeRequest)
// where you need to pass in the exact delegate policy change required.
// Talk to us if you require further information.
const request = await trustVault.replacePublicKeyInDefaultSchedule(walletId, publicKey, sign);
console.info(`Request raised: ${JSON.stringify(request)}`);
return request;
};

const walletId = "eff8cb0f-bda8-4eb9-8a2e-f61b3c837ac5";

// Kick off the wallet policy change
(async () => {
try {
const request = await changeWalletPolicy(walletId, publicKey);
console.info(`Created wallet policy change request: ${request}`);
} catch (e) {
console.info(e);
}

})();

Option 2 - GraphQL to create the Wallet Policy Change

If you don’t want to use the SDK you can use the GraphQL endpoint with any language required.

Mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
mutation($walletId: String!, $delegateSchedules: [[ScheduleInput!]!]!) {
createChangePolicyRequest(
createChangePolicyRequestInput: {
walletId: $walletId
delegateSchedules: $delegateSchedules
}
) {
requests {
walletId
requestId
recovererTrustVaultSignature
unverifiedDigestData{
shaSignData
signData
}
policyTemplate {
expiryTimestamp
delegateSchedules {
keys
quorumCount
}
recovererSchedules {
keys
quorumCount
}
}
}
}
}

Variables

This creates a new delegateSchedule that includes a 1 of 1 for a single external key. Remember the key format should be that of a public key as defined in our glossary. i.e. An ECDSA public key in uncompressed hex format (first byte is always 04), exactly 130 hex characters. This will be the public Key obtained from your AWS KMS implementation.

For details on a more complex delegate schedule, or for how to find your walletId, please reach out for help.

1
2
3
4
5
6
7
8
9
10
{
"walletId": "<walletId>",
"delegateSchedules": [
[
{
"quorumCount": 1,
"keys": ["04fd8a5ac45dcdaa4a975e4cc1cc32d08c4f67705bd3bd61fe6d7e03f82af34c2881a287d625803d6ff4e7857904b75290e859f6c10f49f38f69fa77777672262a"]
}
]
]

Once submitted you will have a the requestId which you should save to use in the next mutation.

Add your signature to the wallet policy change

Mutation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mutation(
$requestId: String!
$publicKeySignaturePairs: [PublicKeySignaturePair!]!
) {
addSignature(
addSignatureInput: {
requestId: $requestId
signRequests: [
{
publicKeySignaturePairs: $publicKeySignaturePairs
}
]
}
) {
requestId
}
}

Variables

  • The requestId is obtained from the mutation to create the policy change.
  • the publicKey is your publicKey in the format mentioned above.
  • the signature is your signature in raw format. This should be the r and s value concatenated.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "requestId": "e125d4ea-1ce5-0e69-275f-a11939c33e1a",
    "publicKeySignaturePairs": [
    {
    "publicKey": "04fd8a5ac45dcdaa4a975e4cc1cc32d08c4f67705bd3bd61fe6d7e03f82af34c2881a287d625803d6ff4e7857904b75290e859f6c10f49f38f69fa77777672262a",
    "signature": "2f660e6ae78cbfc33c21d1cd9ff9bc16f51b51163aa375ce1819aa8fdc199a2021dc85b41b86a7a5efc75b595dfa9b8d2e93553831cce86e4a727b6153dd253b"
    }
    ]
    }

Finishing up

Once you have created your new wallet policy and signed it with your external key you will need to wait for any other policy delegates to sign the request.

Once they have all signed the final step is for Bitpanda Custody to complete some checks before signing the change. Once that has been completed the new wallet policy is ready to be used for signing transactions.

NB: In our Sandbox environment Bitpanda Custody does not need to sign wallet change requests so they will be processed (if correctly signed) within a few minutes.