Example #1
0
def main():
    # create the APIs
    api = LedgerApi(HOST, PORT)

    # we generate an identity from a known key, which contains funds.
    multi_sig_identity = Entity.from_hex(
        "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b")

    # generate a board to control multi-sig account, with variable voting weights
    board = [
        Entity.from_hex(
            "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8"
        ),
        Entity.from_hex(
            "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860"
        ),
        Entity.from_hex(
            "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3"
        ),
        Entity.from_hex(
            "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb"
        ),
    ]

    voting_weights = {
        board[0]: 1,
        board[1]: 1,
        board[2]: 1,
        board[3]: 2,
    }

    # generate another entity as a target for transfers
    other_identity = Entity.from_hex(
        "7da0e3fa62a916238decd4f54d43301c809595d66dd469f82f29e076752b155c")

    # submit deed
    print("\nCreating deed...")
    deed = Deed()
    for sig, weight in voting_weights.items():
        deed.set_signee(sig, weight)
    deed.set_operation(Operation.amend, 4)
    deed.set_operation(Operation.transfer, 3)

    api.sync(api.tokens.deed(multi_sig_identity, deed, 500))

    # display balance before
    print("\nBefore remote-multisig transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))
    print()

    # scatter/gather example
    print("Generating transaction and distributing to signers...")

    # add intended signers to transaction
    ref_tx = TokenTxFactory.transfer(multi_sig_identity,
                                     other_identity,
                                     250,
                                     20,
                                     signatories=board)
    api.set_validity_period(ref_tx)

    # make a reference payload that can be used in this script for validation
    reference_payload = ref_tx.encode_payload()

    # have signers individually sign transaction
    signed_txs = []
    for signer in board:
        # signer builds their own transaction to compare to note that each of the signers will need to agree on all
        # parts of the message including the validity period and the counter
        signer_tx = TokenTxFactory.transfer(multi_sig_identity,
                                            other_identity,
                                            250,
                                            20,
                                            signatories=board)
        signer_tx.counter = ref_tx.counter
        signer_tx.valid_until = ref_tx.valid_until
        signer_tx.valid_from = ref_tx.valid_from

        # sanity check each of the signers payload should match the reference payload
        assert signer_tx.encode_payload() == reference_payload

        # signers locally sign there version of the transaction
        signer_tx.sign(signer)

        # simulate distribution of signed partial transactions
        signed_txs.append(signer_tx.encode_partial())

    # gather and encode final transaction - this step in theory can be done by all the signers provided they are
    # received all the signature shares
    print("Gathering and combining signed transactions...")
    partial_txs = [Transaction.decode_partial(s)[1] for s in signed_txs]

    # merge them together into one fully signed transaction
    success, tx = Transaction.merge(partial_txs)
    assert success  # this indicates that all the signatures have been merged and that the transaction now validates

    # submit the transaction
    api.sync(api.submit_signed_tx(tx))

    print("\nAfter remote multisig-transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # round-robin example
    print("\nGenerating transaction and sending down the line of signers...")

    # create the basis for the transaction
    tx = TokenTxFactory.transfer(multi_sig_identity,
                                 other_identity,
                                 250,
                                 20,
                                 signatories=board)
    api.set_validity_period(tx)

    # serialize and send to be signed
    tx_payload = tx.encode_payload()

    # have signers individually sign transaction and pass on to next signer
    for signer in board:
        # build the target transaction
        signer_tx = Transaction.decode_payload(tx_payload)

        # Signer decodes payload to inspect transaction
        signer_tx.sign(signer)

        # ensure that when we merge the signers signature into the payload that it is correct
        assert tx.merge_signatures(signer_tx)

    # once all the partial signatures have been merged then it makes sense
    print("Collecting final signed transaction...")
    assert tx.is_valid()
    api.sync(api.submit_signed_tx(tx))

    print("\nAfter remote multisig-transfer")
    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))
Example #2
0
def main():
    # create the APIs
    api = LedgerApi(HOST, PORT)

    # generate an identity from a known key, which contains funds.
    multi_sig_identity = Entity.from_hex(
        "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b")
    # generate a board to control multi-sig account, with variable voting weights.
    # we use keys for accounts which already have funds.
    board = []
    board.append(
        Entity.from_hex(
            "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8")
    )
    board.append(
        Entity.from_hex(
            "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860")
    )
    board.append(
        Entity.from_hex(
            "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3")
    )
    board.append(
        Entity.from_hex(
            "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb")
    )
    voting_weights = {
        board[0]: 1,
        board[1]: 1,
        board[2]: 1,
        board[3]: 2,
    }
    # generate another entity as a target for transfers
    other_identity = Entity.from_hex(
        "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8")

    print('Original balance of multi_sig_identity:',
          api.tokens.balance(multi_sig_identity))

    # transfers can happen normally without a deed
    print('\nSubmitting pre-deed transfer with original signature...')
    api.sync(api.tokens.transfer(multi_sig_identity, other_identity, 250, 20))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # submit the original deed
    print("\nCreating deed...")
    deed = Deed()
    for sig, weight in voting_weights.items():
        deed.set_signee(sig, weight)
    # set our initial voting thresholds
    deed.set_operation(Operation.transfer, 2)
    deed.set_operation(Operation.amend, 4)

    api.sync(api.tokens.deed(multi_sig_identity, deed, 6000))

    # original address can no longer validate transfers
    print("\nTransfer with original signature should fail...")
    try:
        api.sync(
            api.tokens.transfer(multi_sig_identity, other_identity, 250, 20))
    except RuntimeError as e:
        print("Transaction failed as expected")
    else:
        print("Transaction succeeded, it shouldn't have")

    # sufficient voting power required to sign transfers
    print("\nSubmitting transfer with two signatures with total 2 votes...")
    print_signing_votes(voting_weights, board[:2])

    # since we now want to create a transaction which has only been signed by a subset of the board, we must use
    # the factory interface in order to build out the transaction we are after
    tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20,
                                 board[:2])
    tx.valid_until = api.tokens.current_block_number() + 100
    # the signatories to sign the transaction
    for signatory in board[:2]:
        tx.sign(signatory)

    api.sync(api.submit_signed_tx(tx))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # some entities may have more voting power
    print("\nSubmitting transfer with single signature with 2 votes...")
    print_signing_votes(voting_weights, board[3])

    tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20,
                                 [board[3]])
    tx.valid_until = api.tokens.current_block_number() + 100
    tx.sign(board[3])
    api.sync(api.submit_signed_tx(tx))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # amend the deed
    print("\nAmending deed to increase transfer threshold to 3 votes...")
    deed.set_operation(Operation.transfer, 3)
    tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board)
    tx.valid_until = api.tokens.current_block_number() + 100
    for member in board:
        tx.sign(member)
    api.sync(api.submit_signed_tx(tx))

    # single member no longer has enough voting power
    print("\nSingle member transfer with 2 votes should no longer succeed...")
    try:
        print_signing_votes(voting_weights, board[3])

        tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250,
                                     20, [board[3]])
        tx.valid_until = api.tokens.current_block_number() + 100
        tx.sign(board[3])
        api.sync(api.submit_signed_tx(tx))

    except RuntimeError as e:
        print("Transaction failed as expected")
    else:
        print("Transaction succeeded, it shouldn't have")

    # correct number of signatory votes
    print("\nSuccesful transaction with sufficient voting weight...")
    print_signing_votes(voting_weights, board[1:])

    tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20,
                                 board[1:])
    tx.valid_until = api.tokens.current_block_number() + 100
    for member in board[1:]:
        tx.sign(member)
    api.sync(api.submit_signed_tx(tx))

    print('Balance 1:', api.tokens.balance(multi_sig_identity))
    print('Balance 2:', api.tokens.balance(other_identity))

    # warning: if no amend threshold is set, future amendments are impossible
    print("\nAmending deed to remove threshold...")
    deed.remove_operation(Operation.amend)
    deed.require_amend = False

    tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board)
    tx.valid_until = api.tokens.current_block_number() + 100
    for member in board:
        tx.sign(member)
    api.sync(api.submit_signed_tx(tx))

    deed.set_operation(Operation.amend, 1)
    print("\nExpecting further amendment to fail...")
    try:
        tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board)
        tx.valid_until = api.tokens.current_block_number() + 100
        for member in board:
            tx.sign(member)
        api.sync(api.submit_signed_tx(tx))
    except RuntimeError as e:
        print("Transaction failed as expected")
    else:
        print("Transaction succeeded, it shouldn't have")