Skip to content

Commit

Permalink
MultisigAddress & MultisigSignature Implementation (#290)
Browse files Browse the repository at this point in the history
* Multisig (#1)

* Add Multisig Support

Co-authored-by: syuan100 <[email protected]>
Co-authored-by: Joe <[email protected]>

* Adding integration test for signing payment transaction

* Cleanup from PR Feedback

* More linting updates

* More linting cleanup

* Linting fixes and other refactoring / updates

* Fix typo and more lint fixes

* fix lint errors (#2)

* Update README.md to include multi-signature example

* Update README with more descriptive comments

* Update README multisig example

* Updating MultisigSignature to include sign method to match SignableKeypair interface

* README Update for MultiSignature example

* Updating MultisigSignature method signatures and update usage and documentation

* Fix merge conflicts, use async verify (#3)

* Add message for burn verification.

* Verify burn txn.

* Create signature verification utility.

* Move @helium/crypto to dev dependencies.

* Remove old code.

* v4.1.0

Co-authored-by: Matt Reetz <[email protected]>

Co-authored-by: syuan100 <[email protected]>
Co-authored-by: Joe <[email protected]>
Co-authored-by: Tyler Whitman <[email protected]>
Co-authored-by: Andrew Allen <[email protected]>
Co-authored-by: Matt Reetz <[email protected]>
  • Loading branch information
6 people authored May 9, 2022
1 parent 92a035b commit 9f9ebe7
Show file tree
Hide file tree
Showing 46 changed files with 915 additions and 464 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"semi": [2, "never"],
"@typescript-eslint/semi": [2, "never"],
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }]
"lines-between-class-members": ["error", "always", { "exceptAfterSingleLine": true }],
"import/no-cycle": "off"
}
}
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,61 @@ client.transactions.submit(signedPaymentTxn.toString())

> ⚠️ Note that oracle prices change over time. It's possible for a transaction to fail if the oracle price changes in between the time the transaction is constructed and when it is absorbed by the consensus group. The API exposes what the next oracle price will be at `https://api.helium.io
/v1/oracle/predictions`. See https://developer.helium.com/blockchain/api/oracle for more details. To avoid failed transactions, it may be worth querying both the oracle predictions, and the current oracle value, and taking the greatest of those values.

### Sending payment from multi-signature wallet
Sending a payment from a multi-signature wallet requires collecting the minimum number of required signatures from the addresses associated with the multi-signature address in question. Below is an example of creating and signing a 1 of 2 multi-signature address transaction.
```js
import { Keypair, MultisigSignature } from '@helium/crypto'
import Address, { MultisigAddress } from '@helium/address'
import { PaymentV2, Transaction } from '@helium/transactions'
import { Client } from '@helium/http'

const client = new Client()

// the transactions library needs to be configured
// with the latest chain vars in order to calcluate fees
const vars = await client.vars.get()
Transaction.config(vars)

// initialize a keypair available for signing
const bob = await Keypair.fromWords(['one', 'two', ..., 'twelve'])

// initialize an address from a b58 string
const alice = Address.fromB58('148d8KTRcKA5JKPekBcKFd4KfvprvFRpjGtivhtmRmnZ8MFYnP3')

// initialize multisig address with the full set of addreses and required number of signatures
const multisigAddress = await MultisigAddress.create([bob.address, alice], 1)

// get the speculative nonce for the multisig keypair
const account = await client.accounts.get(multisigAddress.b58)

// create random payee address
const payeeAddress = Address.fromB58('13dSybmfNofup3rBGat2poGfuab4BhYZNKUJFczSi4jcwLmoXvD')

// construct a PaymentV2 txn to sign
const paymentTxn = new PaymentV2({
payer: multisigAddress,
payments: [
{
payee: payeeAddress,
amount: amountToSend,
},
],
nonce: account.speculativeNonce + 1,
})

// Create signatures payload, a map of address to signature, and finally a KeySignature list
const bobSignature = (await paymentTxn.sign({ payer: bob })).signature || new Uint8Array()
const signatureMap = new Map([[bob.address, await bob.sign(paymentTxn.serialize())]])
const signatures = KeySignature.fromMap([bob.address, aliceAddress], signatureMap)

// Construct multisig signature using the address, the full set of all addresses, and the required signatures
const multisigSig = new MultisigSignature([bob.address, aliceAddress], signatures)

// Update signature on payment trasnaction
await paymentTxn.sign({payer: multisigSig})

// submit the serialized txn to the Blockchain HTTP API
client.transactions.submit(paymentTxn.toString())

```
3 changes: 3 additions & 0 deletions integration_tests/fixtures/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export const aliceWords = [

export const bobB58 = '13M8dUbxymE3xtiAXszRkGMmezMhBS8Li7wEsMojLdb4Sdxc4wc'
export const aliceB58 = '148d8KTRcKA5JKPekBcKFd4KfvprvFRpjGtivhtmRmnZ8MFYnP3'
export const bobAliceMultisig2of2B58 = '1SYJnDnV2G1HSzoBF9nwd5apBX3pS7nLeLkjnVXemBZTP8C8F44TBYnr'
export const bobAliceMultisig1of2B58 = '1SVRdbavwiw4SM6cQFq6DN2nhK4YSqTd7cPhELjshVxzdQvoKbhQWocF'
export const testnetBobAliceMultisig2of2B58 = '14x4TpdfsLeL9MMcaJp6EVXFnA5tsgqXCr2u8MCL4qMEpKEYPCHZEEGJo'

export const bobBip39Words = bobWords.map((word) => (word !== 'energy' ? word : 'episode'))

Expand Down
5 changes: 4 additions & 1 deletion integration_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
"@helium/http": "^4.1.0",
"@helium/transactions": "^4.1.0"
},
"version": "4.1.0"
"version": "4.1.0",
"devDependencies": {
"nock": "^13.2.4"
}
}
42 changes: 40 additions & 2 deletions integration_tests/tests/create_and_submit_payment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import nock from 'nock'
import { Keypair } from '@helium/crypto'
import Address from '@helium/address'
import { Keypair, MultisigSignature } from '@helium/crypto'
import Address, { MultisigAddress } from '@helium/address'
import { Client } from '@helium/http'
import { PaymentV1, PaymentV2 } from '@helium/transactions'
import KeySignature from '@helium/crypto/build/KeySignature'
import { bobWords, bobBip39Words, aliceB58 } from '../fixtures/users'

test('create and submit a payment txn', async () => {
Expand Down Expand Up @@ -96,3 +97,40 @@ test('using the bip39 checksum word should match serialization', async () => {
'wgGOAQohATUaccIv7+wiMZNq0oJrIX7OOdn3f8bEljmSYpnDhpKVEiUKIQGcZZ1yPMHoEKcuePfer0c2qH8Q74/PyAEAtTMn5+5JpBAKIAEqQK88GjmG9CrESHVdcL//ZfWD+KsBnbKmZqKlx8oD89FUms7OjZNcL5NiQ4o0jREg+ahkjc2jX4SgKBBniM+QoAA=',
)
})

test('create and sign multisig payment', async () => {
const bob = await Keypair.fromWords(bobBip39Words)
const aliceAddress = Address.fromB58(aliceB58)

const multisigAddress = await MultisigAddress.create([bob.address, aliceAddress], 1)

const paymentTxn = new PaymentV2({
payer: multisigAddress,
payments: [
{
payee: aliceAddress,
amount: 10,
},
],
nonce: 1,
})
// Sign with bob keypair and create signature for multisig
const bobSignedTransaction = await paymentTxn.sign({ payer: bob })
const bobSignature : Uint8Array = bobSignedTransaction.signature || new Uint8Array()

// Create map of address to signature and convert to KeySignature list
const signatureMap = new Map([[bob.address, bobSignature]])
const signatures = KeySignature.fromMap([bob.address, aliceAddress], signatureMap)

// Construct multisig signature and verify
const multisigSig = new MultisigSignature([bob.address, aliceAddress], signatures)
expect(multisigSig.isValid(multisigAddress)).toBeTruthy()

// Sign payment trasnaction with multisig signature
await paymentTxn.sign({ payer: multisigSig })

const serializedTxn = paymentTxn.toString()
expect(serializedTxn).toBe(
'wgHXAQolAgECEiBqqzKbCO7og1KrG7VnpqrgT+wIowchqqdNAdWDQAa5HRIlCiEBnGWdcjzB6BCnLnj33q9HNqh/EO+Pz8gBALUzJ+fuSaQQCiABKoQBATUaccIv7+wiMZNq0oJrIX7OOdn3f8bEljmSYpnDhpKVAZxlnXI8wegQpy54996vRzaofxDvj8/IAQC1Myfn7kmkAEDfDD6a0GpxsreMPBmr+VACsNHtdEpBnCL1RUzTvqS6N7x9dmSEt8SZeqlTFTmzaLoC8zi4OCNf6zcf+Z347fsH',
)
})
3 changes: 1 addition & 2 deletions integration_tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{
"extends": "../../tsconfig.json",
"include": ["./tests"]
"extends": "../tsconfig.json",
}
Loading

0 comments on commit 9f9ebe7

Please sign in to comment.