forked from helium/helium-js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add Multisig Support Co-authored-by: syuan100 <syuan100@gmail.com> Co-authored-by: Joe <joe@cryptoballoon.net>
- Loading branch information
1 parent
d1c427a
commit e1964ee
Showing
15 changed files
with
647 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
export const ECC_COMPACT_KEY_TYPE = 0 | ||
export const ED25519_KEY_TYPE = 1 | ||
export const MULTISIG_KEY_TYPE = 2 | ||
|
||
export const SUPPORTED_KEY_TYPES = [ | ||
ECC_COMPACT_KEY_TYPE, | ||
ED25519_KEY_TYPE, | ||
MULTISIG_KEY_TYPE, | ||
] | ||
|
||
export type KeyType = number |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { | ||
bs58M, | ||
bs58N, | ||
bs58Version, | ||
bs58MultisigPublicKey, | ||
bs58NetType, | ||
byteToNetType, | ||
byteToKeyType, | ||
sortAddresses, | ||
bs58KeyType, | ||
} from './utils' | ||
import { sha256 } from 'multiformats/hashes/sha2' | ||
import { MULTISIG_KEY_TYPE } from './KeyTypes' | ||
import { NetType, MAINNET} from './NetTypes' | ||
import Address from './Address' | ||
|
||
export class MultisigAddress extends Address { | ||
public M!: number | ||
|
||
public N!: number | ||
|
||
public constructor(version: number, netType: NetType, M: number, N: number, publicKey: Uint8Array) { | ||
if (M > 256) { | ||
throw new Error('required signers cannot exceed 256') | ||
} | ||
if (N > 256) { | ||
throw new Error('total signers cannot exceed 256') | ||
} | ||
if (M > N) { | ||
throw new Error('required signers cannot exceed total signers') | ||
} | ||
super(version, netType, MULTISIG_KEY_TYPE, publicKey); | ||
this.M = M | ||
this.N = N | ||
} | ||
|
||
get bin(): Buffer { | ||
return Buffer.concat([ | ||
// eslint-disable-next-line no-bitwise | ||
Buffer.from([this.netType | this.keyType]), | ||
Buffer.from(new Uint8Array([this.M])), | ||
Buffer.from(new Uint8Array([this.N])), | ||
Buffer.from(this.publicKey), | ||
]) | ||
} | ||
|
||
static fromB58(b58: string): MultisigAddress { | ||
const keyType = bs58KeyType(b58) | ||
if (keyType !== MULTISIG_KEY_TYPE) { | ||
throw new Error('invalid keytype for multisig address') | ||
} | ||
const version = bs58Version(b58) | ||
const netType = bs58NetType(b58) | ||
const M = bs58M(b58) | ||
const N = bs58N(b58) | ||
const publicKey = bs58MultisigPublicKey(b58) | ||
return new MultisigAddress(version, netType, M, N, publicKey) | ||
} | ||
|
||
static fromBin(bin: Buffer): MultisigAddress { | ||
const version = 0 | ||
const byte = bin[0] | ||
const netType = byteToNetType(byte) | ||
const keyType = byteToKeyType(byte) | ||
if (keyType !== MULTISIG_KEY_TYPE) { | ||
throw new Error('invalid keytype for multisig address') | ||
} | ||
const M = bin[1] | ||
const N = bin[2] | ||
const publicKey = bin.slice(3, bin.length) | ||
return new MultisigAddress(version, netType, M, N, publicKey) | ||
} | ||
|
||
public static async create(addresses: Address[], M: number, netType?: NetType): Promise<MultisigAddress> { | ||
const version = 0 | ||
if (!netType) { | ||
netType = MAINNET | ||
} | ||
|
||
let multisigPubKeysBin = new Uint8Array() | ||
for (const address of sortAddresses(addresses)) { | ||
if (address.keyType === MULTISIG_KEY_TYPE) { | ||
return Promise.reject(new Error('cannot craeate multisig with invalid child keytype')) | ||
} | ||
multisigPubKeysBin = new Uint8Array([...multisigPubKeysBin, ...address.bin]) | ||
} | ||
|
||
const publicKey = (await sha256.digest(multisigPubKeysBin)) | ||
return new MultisigAddress(version, netType, M, addresses.length, publicKey.bytes) | ||
} | ||
|
||
static isValid(b58: string): boolean { | ||
try { | ||
MultisigAddress.fromB58(b58) | ||
return true | ||
} catch (error) { | ||
return false | ||
} | ||
} | ||
} | ||
|
||
export default MultisigAddress |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import Address from '..' | ||
import { MultisigAddress } from '../MultisigAddress' | ||
import { | ||
bobB58, | ||
aliceB58, | ||
bobAliceMultisig1of2B58, | ||
bobAliceMultisig2of2B58, | ||
testnetBobAliceMultisig2of2B58, | ||
} from '../../../../integration_tests/fixtures/users' | ||
import { TESTNET } from '../NetTypes' | ||
|
||
|
||
describe('multisig b58', () => { | ||
it('returns a b58 check encoded representation of a multisig address', async () => { | ||
const addressMultisig2of2 = await MultisigAddress.create([Address.fromB58(bobB58), Address.fromB58(aliceB58)], 2) | ||
expect(addressMultisig2of2.b58).toBe(bobAliceMultisig2of2B58) | ||
}) | ||
|
||
it('supports multisig addresses', () => { | ||
const addressMultisig2of2 = MultisigAddress.fromB58(bobAliceMultisig2of2B58) | ||
expect(addressMultisig2of2.b58).toBe(bobAliceMultisig2of2B58) | ||
}) | ||
}) | ||
|
||
describe('bin', () => { | ||
it('returns a binary representation of the multisig ddress', async () => { | ||
const addressMultisig2of2 = await MultisigAddress.create([Address.fromB58(bobB58), Address.fromB58(aliceB58)], 2) | ||
expect(addressMultisig2of2.bin[0]).toBe(2) | ||
}) | ||
}) | ||
|
||
describe('fromBin', () => { | ||
it('builds a MultisigAddress from a binary representation', async () => { | ||
const multisigAddress = await MultisigAddress.create([Address.fromB58(bobB58), Address.fromB58(aliceB58)], 2) | ||
const multisigAddressFromBin = MultisigAddress.fromBin(multisigAddress.bin) | ||
expect(multisigAddressFromBin.b58).toBe(multisigAddress.b58) | ||
}) | ||
}) | ||
|
||
describe('fromB58', () => { | ||
it('builds an Address from a b58 string', () => { | ||
const multisigAddressFromB58 = Address.fromB58(bobAliceMultisig2of2B58) | ||
expect(multisigAddressFromB58.b58).toBe(bobAliceMultisig2of2B58) | ||
}) | ||
}) | ||
|
||
describe('unsupported child key types', () => { | ||
it('throws an error if creating address with multisig key type', async () => { | ||
expect(async () => { | ||
await MultisigAddress.create([MultisigAddress.fromB58(bobAliceMultisig2of2B58), Address.fromB58(aliceB58)], 2) | ||
}).rejects.toThrow() | ||
}) | ||
}) | ||
|
||
describe('isValid', () => { | ||
it('returns true if the address is valid and supported', () => { | ||
expect(MultisigAddress.isValid(bobAliceMultisig2of2B58)).toBeTruthy() | ||
expect(MultisigAddress.isValid(bobAliceMultisig1of2B58)).toBeTruthy() | ||
}) | ||
}) | ||
|
||
describe('testnet addresses', () => { | ||
it('decodes testnet addresses from b58', async () => { | ||
const address = MultisigAddress.fromB58(testnetBobAliceMultisig2of2B58) | ||
expect(address.netType).toBe(TESTNET) | ||
}) | ||
}) | ||
|
||
describe('testnet addresses', () => { | ||
it('decodes testnet addresses from b58', async () => { | ||
const address = MultisigAddress.fromB58(testnetBobAliceMultisig2of2B58) | ||
expect(address.netType).toBe(TESTNET) | ||
}) | ||
}) | ||
|
||
describe('erlang interop', () => { | ||
it('makes the same multisig key as the erlang lib ', async () => { | ||
const keys = [ | ||
Address.fromB58('11MJXxoWFp2bMsqKM6QZin6ync9DQ3fjjFjUrFiRCaKunmBEBhK'), | ||
Address.fromB58('11x7jP9yAnyk5jeYywmsYDFdYq5xvKLKjP2zjhGzCwDSQtxcUDt'), | ||
] | ||
const address = await MultisigAddress.create(keys, 1) | ||
expect(address.b58).toBe('1SVRdbaAev7zSpUsMjvQrbRBGFHLXEa63SGntYCqChC4CTpqwftTPGbZ') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.