Multi-signature

Background

Note

Since v3.5 In the past version, the transactions created in one account can only be signed by one private key, an account can only be managed by one private key. Since V3.5, an account can be managed by several private keys, and the transactions created in one account can be signed by serval private keys.

Reference: TIP-16: Account Multi-signature

Concept

There are three types of permission: owner、witness and active. Owner permission has the right to execute all the contracts. Witness permission is for SR. Active permission contains a set of contracts selected execution permissions.

Protocol Definition

Account

message Account {
  // ...
  Permission owner_permission = 31;
  Permission witness_permission = 32;
  repeated Permission active_permission = 33;
}

Three attributes are added, owner_permission、witness_permission and active_permission. active_permission is a list, the length can not be bigger than 8.

ContractType

message Transaction {
  message Contract {
    enum ContractType {
      AccountCreateContract = 0;
      // ...
      AccountPermissionUpdateContract = 46;
    }
  }
}

AccountPermissionUpdateContract is a new contract type used to update the account permission.

AccountPermissionUpdateContract

message AccountPermissionUpdateContract {
  bytes owner_address = 1;
  Permission owner = 2;
  Permission witness = 3;
  repeated Permission actives = 4;
}
  • owner_address: The account applies multi-signatures
  • owner: Owner permission
  • witness: Witness permission (if is witness)
  • actives: Active permission

This will override the Original account permission.

Permission

message Permission {
  enum PermissionType {
    Owner = 0;
    Witness = 1;
    Active = 2;
  }
  PermissionType type = 1;
  int32 id = 2;
  string permission_name = 3;
  int64 threshold = 4;
  int32 parent_id = 5;
  bytes operations = 6;
  repeated Key keys = 7;
}
  • PermissionType: Permission type
  • id: Generated by system. Owner id=0, Witness id=1, Active id increases from 2. Specifying using which permission to execute a contract by setting id. For instance, using owner permission, set id=0
  • permission_name: Permission name, 32 bytes length limit
  • threshold: The threshold of the signature weight
  • parent_id: Current 0
  • operations: 32 bytes (256 b), each bit represent the execution permission of one contract, 1 means it owns the execution permission of the contract.

    For instance, operations=0x0100...00(hex), 100...0(binary), refer to the definition of Transaction.ContractType in proto, the id of AccountCreateContract is 0, means this permission only owns the execution permission of AccountCreateContract

  • keys: The accounts and weights that all own the permission, 5 keys at most.

Key

message Key {
  bytes address = 1;
  int64 weight = 2;
}
  • address: The account address
  • weight: The signature weight

Transaction

message Transaction {
  // ...
  int32 Permission_id = 5;
}

Permission_id is added. It is corresponding to Permission.id 1 is not allowed, because witness permission is only used to produce blocks, not for transaction signature.

Owner Permission

Owner permission is the top permission of an account. It is used to control account ownership, adjust permission structure. Owner Permission has the right to execute all the contracts.

Owner permission's features:

  1. The account that has owner permission can change the owner permission
  2. When owner permission is null, the default owner of the account owns the owner permission
  3. When you create a new account, the address will be insert into owner permission automatically, default weight is 1, keys field only contains this address and also weight is 1.
  4. If a permissionId is not specified when a contract is executed, using owner permission by default.

Witness Permission

Super representatives can use this permission to manage block producing. Only witness account has this permission.

Usage scenario example: A super representative deploys a witness node on cloud server. In order to keep the account on the cloud server safe, you can only give the block producing permission to the account you put on cloud server. Because this account only owns block producing permission, no TRX transfer permission, so even if the account on the cloud server is leaked, the TRX will not be lost.

Witness node configuration:

  1. if no witness permission is used, no need to configure
  2. if itness permission is used, need to reconfigure:
# config.conf

// Optional.The default is empty.
// It is used when the witness account has set the witnessPermission.
// When it is not empty, the localWitnessAccountAddress represents the address of the witness account,
// and the localwitness is configured with the private key of the witnessPermissionAddress in the witness account.
// When it is empty,the localwitness is configured with the private key of the witness account.

//localWitnessAccountAddress =

localwitness = [
  f4df789d3210ac881cb900464dd30409453044d2777060a0c391cbdf4c6a4f57
]

Active Permission

Active permission is composed of a set of contract execution permission, like creating an account, transfer function, etc.

Active permission's features:

  1. the account owns owner permission can change active permission
  2. the account owns the execution permission of AccountPermissionUpdateContract can also change active permission
  3. 8 permissions at most
  4. permissionId increases from 2 automatically
  5. when a new account is created, an active permission will be created automatically, and the address will be inserted into it, default weight is 1, keys field only contains this address and weight is 1

Fee

  1. Using AccountPermissionUpdateContract costs 100TRX
  2. If a transaction contains 2 or more than 2 signatures, it charges an extra 1 TRX besides the transaction fee
  3. The fee can be modified by proposing

API

Change Permission

AccountPermissionUpdateContract, steps:

  1. call getaccount to query the account, get the original permission
  2. change permission
  3. build transaction and sign
  4. send transaction

Demo HTTP request:

// POST to http://{{host}}:{{port}}/wallet/accountpermissionupdate

{
  "owner_address": "41ffa9466d5bf6bb6b7e4ab6ef2b1cb9f1f41f9700",
  "owner": {
    "type": 0,
    "id": 0,
    "permission_name": "owner",
    "threshold": 2,
    "keys": [{
        "address": "41F08012B4881C320EB40B80F1228731898824E09D",
        "weight": 1
      },
      {
        "address": "41DF309FEF25B311E7895562BD9E11AAB2A58816D2",
        "weight": 1
      },
      {
        "address": "41BB7322198D273E39B940A5A4C955CB7199A0CDEE",
        "weight": 1
      }
    ]
  },
  "witness": {
      "type": 1,
      "id": 1,
      "permission_name": "witness",
      "threshold": 1,
      "keys": [{
          "address": "41F08012B4881C320EB40B80F1228731898824E09D",
          "weight": 1
        }
      ]
    },
  "actives": [{
    "type": 2,
    "id": 2,
    "permission_name": "active0",
    "threshold": 3,
    "operations": "7fff1fc0037e0000000000000000000000000000000000000000000000000000",
    "keys": [{
        "address": "41F08012B4881C320EB40B80F1228731898824E09D",
        "weight": 1
      },
      {
        "address": "41DF309FEF25B311E7895562BD9E11AAB2A58816D2",
        "weight": 1
      },
      {
        "address": "41BB7322198D273E39B940A5A4C955CB7199A0CDEE",
        "weight": 1
      }
    ]
  }]
}

Calculate the Active Permission's Operations

public static void main(String[] args) {

  //you need to specify the id of the contract you need to give permission to by referring to the definition of Transaction.ContractType in proto to get the id of the contract, below includes all the contract except AccountPermissionUpdateContract(id=46)

  Integer[] contractId = {0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 31,
      32, 33, 41, 42, 43, 44, 45};
  List<Integer> list = new ArrayList<>(Arrays.asList(contractId));
  byte[] operations = new byte[32];
  list.forEach(e -> {
    operations[e / 8] |= (1 << e % 8);
  });

  //7fff1fc0033e0000000000000000000000000000000000000000000000000000
  System.out.println(ByteArray.toHexString(operations));
}

Contract Execution

(1). Create transaction, the same as none multi-signatures

(2). Specify Permission_id, default 0, represent owner permission, demo

(3). User A sign the transaction, and then send it to user B

(4). User B sign the transaction gets from A, and then send it to user C

......

(n). The last users that signs the transaction broadcast it to the node

(n+1). The node will verify if the sum of the weight of all signatures is bigger than threshold, if true, the transaction is accepted, otherwise, is rejected

Demo: MultiSignDemo.java

Other APIs

Please refer to HTTP API and RPC API for more information.

  1. query the addresses that already signed a transaction

    > curl -X POST http://127.0.0.1:8090/wallet/getapprovedlist -d '{"transaction"}'
    
    rpc GetTransactionApprovedList(Transaction) returns (TransactionApprovedList) { }
    
  2. query the signature weight of a transaction

    > curl -X POST http://127.0.0.1:8090/wallet/getsignweight -d '{"transaction"}'
    
    rpc GetTransactionSignWeight (Transaction) returns (TransactionSignWeight) {}
    

Others

Since V3.5, what is the change after a new account is created?

When to create a new account, an owner permission and active permission will be generated automatically. Owner permission only contains one key, the weight and threshold are both 1. Active permission also contains one key, the weight and threshold are both 1, and operations is "7fff1fc0033e0000000000000000000000000000000000000000000000000000", means it support the execution of all contracts except AccountPermissionUpdateContract. After V3.5, if there is a new system contract, the default operations value of the newly created account will change. The operations of existing accounts will not change.

Please refer to wallet-cli to check the usage of multi-signature.

Fees

If you update your account permission, the fee is 100 TRX.

If a transaction is signed by more than 1 account, the fee is 1 TRX.