Multi-Signature#

Introduction#

Multiple signature functions allow for permission grading, and each permission can correspond to multiple private keys. This makes it possible to achieve multi-person joint control of accounts. This guide walks the user through TRON's multi-signature implementation and design.

https://github.com/tronprotocol/TIPs/blob/master/tip-16.md

Design#

The scheme includes the three privilege levels of owner, duration, and active privilege. Owner privilege has the authority to execute all contracts, duration privilege is used for super delegates, and active is a custom privilege (can be combined with permission sets).

Structure Description#

1.Account Modification

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

Three permission attributes are added to the account structure, namely owner_permission, witness_permission, and active_permission, where active_permission is a list and can be specified up to 8.

2.Contract Type Modification

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

Added a transaction type AccountPermissionUpdateContract to update account permissions.

3.AccountPermissionUpdateContract

message AccountPermissionUpdateContract {
   bytes owner_address = 1;
   Permission owner = 2;   
   Permission witness = 3; 
   repeated Permission actives = 4; 
 }
Parameter Description
owner_address The address of the account to be modified
owner Modified owner permission
witness Modified witness permission (if it is a witness)
actives Modified actives permission

This interface overrides the original account permissions, so if you only want to modify the owner permissions, the witness (if it is a witness account) and actives also need to be set.

4.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;// 
 }
Parameter Description
PermissionType Permission type, currently only supports three permissions.
id The value is automatically set by the system, with Owner id=0 and Witness id=1. Active id is incremented from 2 onwards. When the contract is executed, the id is used to specify which permission to use. For example, if the owner permission is used, the id is set to 0.
permission_name Permission name, set by the user, limited to 32 bytes in length.
threshold Threshold, the corresponding operation is allowed only when the sum of the weights of the participating signatures exceeds the domain value. Requires a maximum value less than the Long type.
parent_id Currently only 0
operations A total of 32 bytes (256 bits), each representing the authority of a contract, a 1 means the authority to own the contract. For operations=0x0100...00 (hexadecimal), ie 100...0 (binary), look at the Transaction.ContractType definition in proto. The id of the contract AccountCreateContract is 0, which means that the permission only has the implementation of AccountCreateContract. Permissions can be obtained using the "calculation example of operations in active permissions".
keys The address and weight that jointly own the permission can be up to 5 keys.

5.Key

message Key {
   bytes address = 1;
   int64 weight = 2;
 }
Parameter Description
address Address with this privilege
weight This address has weight for this permission

6.Transaction Modification

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

Add a Permission_id field to the transaction, corresponding to Permission.id, which specifies which permission to use. The default is 0, which is the owner permission. It is not allowed to be 1, because the witness permission is only used for block creation and is not used to sign the transaction.

Owner Permission#

OwnerPermission is the highest privilege of the account, used to control the ownership of the user, adjust the privilege structure, and the Owner privilege can also execute all contracts.

The Owner privilege has the following characteristics:

The OwnerPermission address can be modified by OwnerPermission. When OwnerPermission is empty, the account address is assumed to have owner permission by default. When the account is newly created, the address of the account is automatically filled in the OwnerPermission, and the default domain value is 1, the only address in the keys is included and the weight is 1. When the permissionId is not specified when the contract is executed, OwnerPermission is used by default.

Witness Permission#

The super representative can use this privilege to manage the block nodes. Non-witness accounts do not have this permission.

Example of usage scenario: A super representative deploys a block program on the cloud server. For account security, you can assign the block permission to another address. Since the address only has the outbound permission, there is no TRX rollout permission, and even if the private key on the server is compromised, TRX will not be lost.

Witness block production node configuration:

No special configuration is required when the witness permissions are not modified. The block node modified to witness permission needs to be reconfigured. The configuration items are as follows:

#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.
// Optional, default is empty.
// Used to set the durationPermission when the witness account is set.
// When the value is not empty, localWitnessAccountAddress represents the address of the witness account, and localwitness is the private key of the address in the durationPermission.
// When the value is empty, localwitness is configured as the private key of the witness account.

//localWitnessAccountAddress =

localwitness = [
  f4df789d3210ac881cb900464dd30409453044d2777060a0c391cbdf4c6a4f57
]
Active Permissions#

Active permission is used to provide a combination of permissions, such as providing a permission to perform only the creation of accounts and transfer functions.

Active permissions have the following features:

With the address of OwnerPermission can modify Active permissions The address with the permission to execute AccountPermissionUpdateContract can also modify Active permissions Support up to 8 combinations. The id of the permission is automatically incremented from 2. When the account is newly created, an Active permission is automatically created, and the address of the account is filled in. The default domain value is 1, and only the account address is included in the keys and the weight is 1. Cost When using the account update permission, that is, the AccountPermissionUpdate contract, 100TRX is charged. When using a multi-signature transaction, that is, a transaction that includes two or more signatures in the transaction, in addition to the transaction fee, 1TRX is charged. The above fees can be revised by the proposal.

API#

Modify Permissions#

AccountPermissionUpdateContract, modify the permissions steps are as follows:

Use the interface getaccount to query the account and get the original permissions Modify the permission Create a contract, signature Send a transaction http-demo

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


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

For the definition and limitations of the parameter fields, please see Structure Description.

Example of calculation of operations in active authority

public static void main(String[] args) {

  // Specify the contract id to be supported (see the Transaction.ContractType definition in proto), which contains all contracts 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);
  });

  //7fff1fc0037e0000000000000000000000000000000000000000000000000000
  System.out.println(ByteArray.toHexString(operations));
}
Contract Execution#

Create a transaction, the same as the construction process of a non-multiple signature transaction 2, specify the Permission_id, the default is 0, indicating the owner-permission The user A signs the post-signature transaction to B through other means. User B signs, and the signed transaction is sent to C by other means. ... n. The last user who completed the signature broadcasts the transaction to the node. N+1, verify that the sum of the weights of the multi-signature is greater than the domain value, accept the transaction, otherwise reject the transaction Code example:

https://github.com/tronprotocol/wallet-cli/blob/multi_sign_V2/src/main/java/org/tron/demo/MultiSignDemo.java

Other New Interfaces#

For detailed interface description, please see Tron-http.md and wave field wallet RPC-API.md

Increase Signature

curl -X POST  http://127.0.0.1:8090/wallet/addtransactionsign -d '{"transaction": "TransferContract", "privateKey": "permissionkey1"}'

rpc AddSign (TransactionSign) returns (TransactionExtention) {}

Query Signed Address

curl -X POST  http://127.0.0.1:8090/wallet/getapprovedlist -d '{"transaction"}'

rpc GetTransactionApprovedList(Transaction) returns (TransactionApprovedList) { }

Query Transaction Signature Weight

curl -X POST  http://127.0.0.1:8090/wallet/getsignweight -d '{"transaction"}'

rpc GetTransactionSignWeight (Transaction) returns (TransactionSignWeight) {}

The owner-permission and an active-permission are automatically generated during account creation. The owner-permission contains one key, with the permissions and thresholds both set as 1. The active-permission also contains a key with permissions and thresholds set at 1.

The operations are "7fff1fc0037e0000000000000000000000000000000000000000000000000000", which means all operations except AccountPermissionUpdateContract are supported.

Example Proccess Flow#

Multi-Signature Example Process Flow#

TronWeb allows developers to easily perform multi-signing with the tronWeb.trx.multiSign method. The is an example of the workflow.

1.Create transaction JavaScript

const originalTransaction = await tronWeb.transactionBuilder.sendTrx('41e0d5217904dcb2d5453c2359b86df9673046c4ce', 100000, '4164eb61f763d3374a998989f06929c1bad87175ba');

2.Perform the Multi-Signing JavaScript

let signedTransaction = await tronWeb.trx.multiSign(originalTransaction, '47e5e1a590a44e7e6f4349a4e3ea6a4f9a791e3fccb115ffbddffdbf6d0588e6', 2);

signedTransaction = await tronWeb.trx.multiSign(signedTransaction, 'd5f244307d3ab6dc5739b83ec913b662a24f87e873190e9c1a2d9709f579540c', 2);

signedTransaction = await tronWeb.trx.multiSign(signedTransaction, '9944b7010db0d44a861bca112e40365a934727c5c17f8dfd3b9cc7b31e8aeaf1', 2);

Signature result:

JSON

{
  "signature": [
    "2c25a81333fd83edec33ebae16cb3dfb979cfc4ce035665953c2b61179b06cb9f0625c660947404c5a1e17331cc375579bed7295ebe6635eb30ab79e73c16e6a01",
    "86cfad6c7bc086c04c27267ef4a3c5ae3ea394e05e8402713b0b7e624546b76e30518ebf897c7495b29bbb372ba94d65ba927bd48a4fa9c36e439c975b7f88f500",
    "4d980f85de1a1bcc0c45fa118276a3a6319a6cf404ee68f5d460fb075bd41f6a8702e0448db2eb55cd74c4dd85d53909597eb05401a48aaa44bdb3a327e47d5001"
  ],
  "txID": "7034c0a26ffc1010c2cba113d9685cbe464793abe0a1d0d21c2e593df7990a84",
  "raw_data": {
    "contract": [
      {
        "parameter": {
          "value": {
            "amount": 100000,
            "owner_address": "4164eb61f763d3374a998989f06929c1bad87175ba",
            "to_address": "41e0d5217904dcb2d5453c2359b86df9673046c4ce"
          },
          "type_url": "type.googleapis.com/protocol.TransferContract"
        },
        "type": "TransferContract",
        "Permission_id": 2
      }
    ],
    "ref_block_bytes": "02d6",
    "ref_block_hash": "cf96636fd7767b20",
    "expiration": 1555454166000,
    "timestamp": 1555454106555
  },
  "raw_data_hex": "0a0202d62208cf96636fd7767b2040f087acc2a22d5a69080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a154164eb61f763d3374a998989f06929c1bad87175ba121541e0d5217904dcb2d5453c2359b86df9673046c4ce18a08d06280270bbb7a8c2a22d"
}

The method requires passing the original transaction, the private key for signing, and the permission ID. The permission ID should be generated when you update the account permission. You can overview your account permission and check the permission ID by calling the getAccount method.

Notes: If you use active permission, we will re-calculate the transaction ID, which differs from the original transaction after tronWeb.transactionBuilder.sendTrx or other methods.

3.Check Transaction's Sign Weight The function tronweb.trx.getSignWeightallows you to check how many addresses have signed the transaction and the current weight. This function can be run either during the multi-signing process or after its completion.

JavaScript

const signWeight = await tronWeb.trx.getSignWeight(signedTransaction, 2);

Result:

JSON

{
  "result": {

  },
  "approved_list": [
    "4164eb61f763d3374a998989f06929c1bad87175ba",
    "41e0d5217904dcb2d5453c2359b86df9673046c4ce",
    "41e3222fff601087f76ee803c0e09596b21282f10d"
  ],
  "permission": {
    "operations": "7fff1fc0037e0000000000000000000000000000000000000000000000000000",
    "keys": [
      {
        "address": "4164eb61f763d3374a998989f06929c1bad87175ba",
        "weight": 1
      },
      {
        "address": "41e0d5217904dcb2d5453c2359b86df9673046c4ce",
        "weight": 1
      },
      {
        "address": "41e3222fff601087f76ee803c0e09596b21282f10d",
        "weight": 1
      }
    ],
    "threshold": 3,
    "id": 2,
    "type": "Active",
    "permission_name": "active0"
  },
  "current_weight": 3,
  "transaction": {
    "result": {
      "result": true
    },
    "txid": "7100eddcc788b0956e0224470111bffaa99b465781a2abe75cbd2a29c82ade42",
    "transaction": {
      "signature": [
        "bfc89f7a49fa233cfb2484c5e1fdb3c687815c68f198fc81b0cccbc1ccbb09c611d0f3d90a32d7e8dc1d85fde43c10b7cb586edde1833f7974fda42e951b94ed00",
        "8c167cd077d82f5b36e1d2bbc831711523dcc71494830cee6181b1435c44b22b751d6a72b425948db8aac3fb5f736bd2f9e689e18941002cafab1ff3caeb354d01",
        "64921a4760fd2b2fb8f76bb7feab50d49d6298774f120164c050f4e154d624445e9a620b051116ec674f62640adb5ff2d6b0934ba9187db1197d65f368fd5ebc00"
      ],
      "txID": "7100eddcc788b0956e0224470111bffaa99b465781a2abe75cbd2a29c82ade42",
      "raw_data": {
        "contract": [
          {
            "parameter": {
              "value": {
                "amount": 100000,
                "owner_address": "4164eb61f763d3374a998989f06929c1bad87175ba",
                "to_address": "41e0d5217904dcb2d5453c2359b86df9673046c4ce"
              },
              "type_url": "type.googleapis.com/protocol.TransferContract"
            },
            "type": "TransferContract"
          }
        ],
        "ref_block_bytes": "03a9",
        "ref_block_hash": "f04705dfcc42a285",
        "expiration": 1555454799000,
        "timestamp": 1555454740761
      },
      "raw_data_hex": "0a0203a92208f04705dfcc42a2854098d9d2c2a22d5a69080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a154164eb61f763d3374a998989f06929c1bad87175ba121541e0d5217904dcb2d5453c2359b86df9673046c4ce18a08d062802709992cfc2a22d"
    }
  }
}

4.Check Approved List The function tronWeb.trx.getApprovedList allows you to check how many addresses have already signed (approved) the transaction.

JavaScript

const approvedList = await tronWeb.trx.getApprovedList(signedTransaction);

Result:

JSON

{
  "result": {

  },
  "approved_list": [
    "4164eb61f763d3374a998989f06929c1bad87175ba",
    "41e0d5217904dcb2d5453c2359b86df9673046c4ce",
    "41e3222fff601087f76ee803c0e09596b21282f10d"
  ],
  "transaction": {
    "result": {
      "result": true
    },
    "txid": "479d294569f9d16bb9db643383eae40d4d6df0fa081b6918cf5d831f6fcac1bc",
    "transaction": {
      "signature": [
        "fe5eaa06536e431143612d7b967059480ade185c70ad3c7529ed72c91b74b5c705cc231a857247e5cec31cb1aceb2d9016d2fb2bf57124ec5314b2b1688d060701",
        "6951c7e651fdf79102d655acd6ed57e9c2ba8d4e9b7c1486b54c7d9912bf9e58bb5a4876593532f889858e819b41bb9ae60da7693a008fff7d335da34b9088e401",
        "9960d832d5d71556124ea04e783e7e15d4437c27933b90d834670d58f590a3a255d3081d12b7ddccd8a84060ad1294b0c19f9ee63fa12fd5c602695ff32f8b6300"
      ],
      "txID": "479d294569f9d16bb9db643383eae40d4d6df0fa081b6918cf5d831f6fcac1bc",
      "raw_data": {
        "contract": [
          {
            "parameter": {
              "value": {
                "amount": 100000,
                "owner_address": "4164eb61f763d3374a998989f06929c1bad87175ba",
                "to_address": "41e0d5217904dcb2d5453c2359b86df9673046c4ce"
              },
              "type_url": "type.googleapis.com/protocol.TransferContract"
            },
            "type": "TransferContract"
          }
        ],
        "ref_block_bytes": "03e5",
        "ref_block_hash": "3dfe142fc7c242bf",
        "expiration": 1555454979000,
        "timestamp": 1555454919646
      },
      "raw_data_hex": "0a0203e522083dfe142fc7c242bf40b8d7ddc2a22d5a69080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a154164eb61f763d3374a998989f06929c1bad87175ba121541e0d5217904dcb2d5453c2359b86df9673046c4ce18a08d06280270de87dac2a22d"
    }
  }
}

5.Broadcast Transaction Once multi-signing completes, you can broadcast the signed transaction directly and check the transaction by using getTransactionById later.

JavaScript

const result = await = tronWeb.trx.broadcast(signedTransaction);

Result:

JSON

{
  "result": true,
  "transaction": {
    "signature": [
      "9ea568d070de64ce674d9db0d1c0dddbdf83435b4e60b27860fd7a017c071f9858062671ff938600b5cbd8b3475b2ab16df4c2076654f18a928e972ce3fe5e3600",
      "202db297c8b31d51e1ad9406aaad030b602af865810a58d19910858bba3825ef0143b3013b33ad15aa6680b6b735efd1b8d5a913f42ed7d04792b9200f69097401",
      "5c143c98ff4f9a7837eacd8ed26320b4a5544954241ca7985d73e6d099b1f8df0667d686e9e6ab5a2ae6be62e6795523b2cf423b3fc3a88523faf2408dac753001"
    ],
    "txID": "b25f3449384b0ea6b8c201596ed6998fb5581f4a68c5d467a268a4e60499ff1b",
    "raw_data": {
      "contract": [
        {
          "parameter": {
            "value": {
              "amount": 100000,
              "owner_address": "4164eb61f763d3374a998989f06929c1bad87175ba",
              "to_address": "41e0d5217904dcb2d5453c2359b86df9673046c4ce"
            },
            "type_url": "type.googleapis.com/protocol.TransferContract"
          },
          "type": "TransferContract",
          "Permission_id": 2
        }
      ],
      "ref_block_bytes": "0412",
      "ref_block_hash": "614e7424dfcf96fb",
      "expiration": 1555455114000,
      "timestamp": 1555455056464
    },
    "raw_data_hex": "0a0204122208614e7424dfcf96fb4090f6e5c2a22d5a69080112630a2d747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e5472616e73666572436f6e747261637412320a154164eb61f763d3374a998989f06929c1bad87175ba121541e0d5217904dcb2d5453c2359b86df9673046c4ce18a08d06280270d0b4e2c2a22d"
  }
}