mirror of
https://github.com/alexgo-io/stacks.js.git
synced 2026-01-12 17:52:41 +08:00
add P2WSH and P2WPKH address modes for addressFromPublicKeys()
This commit is contained in:
committed by
Reed Rosenbluth
parent
d7f32a24a4
commit
da31bf1c4d
@@ -19,6 +19,8 @@ import {
|
||||
hashP2PKH,
|
||||
rightPadHexToLength,
|
||||
hashP2SH,
|
||||
hashP2WSH,
|
||||
hashP2WPKH,
|
||||
} from './utils';
|
||||
|
||||
import { c32addressDecode, c32address } from 'c32check';
|
||||
@@ -210,11 +212,14 @@ export function addressFromPublicKeys(
|
||||
switch (hashMode) {
|
||||
case AddressHashMode.SerializeP2PKH:
|
||||
return addressFromVersionHash(version, hashP2PKH(publicKeys[0].data));
|
||||
case AddressHashMode.SerializeP2WPKH:
|
||||
return addressFromVersionHash(version, hashP2WPKH(publicKeys[0].data));
|
||||
case AddressHashMode.SerializeP2SH:
|
||||
return addressFromVersionHash(version, hashP2SH(numSigs, publicKeys.map(serializePublicKey)));
|
||||
default:
|
||||
throw Error(
|
||||
`Not yet implemented: address construction using public keys for hash mode: ${hashMode}`
|
||||
case AddressHashMode.SerializeP2WSH:
|
||||
return addressFromVersionHash(
|
||||
version,
|
||||
hashP2WSH(numSigs, publicKeys.map(serializePublicKey))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,21 @@ export const hashP2PKH = (input: Buffer): string => {
|
||||
return hash160(input).toString('hex');
|
||||
};
|
||||
|
||||
// Internally, the Stacks blockchain encodes address the same as Bitcoin
|
||||
// single-sig address over p2sh (p2h-p2wpkh)
|
||||
export const hashP2WPKH = (input: Buffer): string => {
|
||||
const keyHash = hash160(input);
|
||||
|
||||
const bufferArray = new BufferArray();
|
||||
bufferArray.appendByte(0);
|
||||
bufferArray.appendByte(keyHash.length);
|
||||
bufferArray.push(keyHash);
|
||||
|
||||
const redeemScript = bufferArray.concatBuffer();
|
||||
const redeemScriptHash = hash160(redeemScript);
|
||||
return redeemScriptHash.toString('hex');
|
||||
};
|
||||
|
||||
// Internally, the Stacks blockchain encodes address the same as Bitcoin
|
||||
// multi-sig address (p2sh)
|
||||
export const hashP2SH = (numSigs: number, pubKeys: Buffer[]): string => {
|
||||
@@ -135,6 +150,40 @@ export const hashP2SH = (numSigs: number, pubKeys: Buffer[]): string => {
|
||||
return redeemScriptHash.toString('hex');
|
||||
};
|
||||
|
||||
// Internally, the Stacks blockchain encodes address the same as Bitcoin
|
||||
// multisig address over p2sh (p2sh-p2wsh)
|
||||
export const hashP2WSH = (numSigs: number, pubKeys: Buffer[]): string => {
|
||||
if (numSigs > 15 || pubKeys.length > 15) {
|
||||
throw Error('P2WSH multisig address can only contain up to 15 public keys');
|
||||
}
|
||||
|
||||
// construct P2SH script
|
||||
const scriptArray = new BufferArray();
|
||||
// OP_n
|
||||
scriptArray.appendByte(80 + numSigs);
|
||||
// public keys prepended by their length
|
||||
pubKeys.forEach(pubKey => {
|
||||
scriptArray.appendByte(pubKey.length);
|
||||
scriptArray.push(pubKey);
|
||||
});
|
||||
// OP_m
|
||||
scriptArray.appendByte(80 + pubKeys.length);
|
||||
// OP_CHECKMULTISIG
|
||||
scriptArray.appendByte(174);
|
||||
|
||||
const script = scriptArray.concatBuffer();
|
||||
const digest = new sha256().update(script).digest();
|
||||
|
||||
const bufferArray = new BufferArray();
|
||||
bufferArray.appendByte(0);
|
||||
bufferArray.appendByte(digest.length);
|
||||
bufferArray.push(digest);
|
||||
|
||||
const redeemScript = bufferArray.concatBuffer();
|
||||
const redeemScriptHash = hash160(redeemScript);
|
||||
return redeemScriptHash.toString('hex');
|
||||
};
|
||||
|
||||
export function isClarityName(name: string) {
|
||||
const regex = /^[a-zA-Z]([a-zA-Z0-9]|[-_!?+<>=/*])*$|^[-+=/*]$|^[<>]=?$/;
|
||||
return regex.test(name) && name.length < 128;
|
||||
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
addressFromHashMode,
|
||||
createAssetInfo,
|
||||
LengthPrefixedList,
|
||||
addressFromPublicKeys,
|
||||
} from '../src/types';
|
||||
|
||||
import { TransactionVersion, AddressHashMode, StacksMessageType } from '../src/constants';
|
||||
|
||||
import { serializeDeserialize } from './macros';
|
||||
import { BufferReader } from '../src/bufferReader';
|
||||
import { createStacksPublicKey } from '../src/keys';
|
||||
|
||||
test('Length prefixed strings serialization and deserialization', () => {
|
||||
const testString = 'test message string';
|
||||
@@ -105,6 +107,31 @@ test('C32 address hash mode - testnet P2SH', () => {
|
||||
expect(address).toBe(expected);
|
||||
});
|
||||
|
||||
test('C32 address hash mode - mainnet P2WSH', () => {
|
||||
const address = addressToString(
|
||||
addressFromHashMode(
|
||||
AddressHashMode.SerializeP2WSH,
|
||||
TransactionVersion.Mainnet,
|
||||
'55011fc38a7e12f7d00496aef7a1c4b6dfeba81b'
|
||||
)
|
||||
);
|
||||
const expected = 'SM1AG27Y3H9Z15XYG0JBAXXX1RJVDZTX83FA1DDSJ';
|
||||
expect(address).toBe(expected);
|
||||
});
|
||||
|
||||
test('C32 address hash mode - testnet P2WSH', () => {
|
||||
const address = addressToString(
|
||||
addressFromHashMode(
|
||||
AddressHashMode.SerializeP2WSH,
|
||||
TransactionVersion.Testnet,
|
||||
'55011fc38a7e12f7d00496aef7a1c4b6dfeba81b'
|
||||
)
|
||||
);
|
||||
const expected = 'SN1AG27Y3H9Z15XYG0JBAXXX1RJVDZTX83DE2F6ME';
|
||||
expect(address).toBe(expected);
|
||||
});
|
||||
|
||||
|
||||
test('C32check addresses serialization and deserialization', () => {
|
||||
const c32AddressString = 'SP9YX31TK12T0EZKWP3GZXX8AM37JDQHAWM7VBTH';
|
||||
const addr = createAddress(c32AddressString);
|
||||
@@ -122,3 +149,53 @@ test('Asset info serialization and deserialization', () => {
|
||||
expect(deserialized.contractName.content).toBe(assetContractName);
|
||||
expect(deserialized.assetName.content).toBe(assetName);
|
||||
});
|
||||
|
||||
|
||||
// address/mod.rs: test_public_keys_to_address_hash()
|
||||
test('Public keys to address hash', () => {
|
||||
const fixtures = [
|
||||
{
|
||||
keys: [createStacksPublicKey('040fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc806b9a4f07579934774cc0c155e781d45c989f94336765e88a66d91cfb9f060b0')],
|
||||
numRequired: 1,
|
||||
segwit: false,
|
||||
result: '395f3643cea07ec4eec73b4d9a973dcce56b9bf1',
|
||||
},
|
||||
{
|
||||
keys: [
|
||||
createStacksPublicKey('040fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc806b9a4f07579934774cc0c155e781d45c989f94336765e88a66d91cfb9f060b0'),
|
||||
createStacksPublicKey('04c77f262dda02580d65c9069a8a34c56bd77325bba4110b693b90216f5a3edc0bebc8ce28d61aa86b414aa91ecb29823b11aeed06098fcd97fee4bc73d54b1e96')
|
||||
],
|
||||
numRequired: 2,
|
||||
segwit: false,
|
||||
result: 'fd3a5e9f5ba311ce6122765f0af8da7488e25d3a',
|
||||
},
|
||||
{
|
||||
keys: [createStacksPublicKey('020fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc8')],
|
||||
numRequired: 1,
|
||||
segwit: true,
|
||||
result: '0ac7ad046fe22c794dd923b3be14b2e668e50c42',
|
||||
},
|
||||
{
|
||||
keys: [createStacksPublicKey('020fadbbcea0ff3b05f03195b41cd991d7a0af8bd38559943aec99cbdaf0b22cc8'),
|
||||
createStacksPublicKey('02c77f262dda02580d65c9069a8a34c56bd77325bba4110b693b90216f5a3edc0b')],
|
||||
numRequired: 2,
|
||||
segwit: true,
|
||||
result: '3e02fa83ac2fae11fd6703b91e7c94ad393052e2',
|
||||
}
|
||||
];
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
let hashMode;
|
||||
|
||||
if (!fixture.segwit) {
|
||||
if (fixture.numRequired === 1) hashMode = AddressHashMode.SerializeP2PKH;
|
||||
else hashMode = AddressHashMode.SerializeP2SH;
|
||||
} else {
|
||||
if (fixture.numRequired === 1) hashMode = AddressHashMode.SerializeP2WPKH;
|
||||
else hashMode = AddressHashMode.SerializeP2WSH;
|
||||
}
|
||||
|
||||
const address = addressFromPublicKeys(0, hashMode, fixture.numRequired, fixture.keys);
|
||||
expect(address.hash160).toBe(fixture.result);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user