-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
177 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { | ||
and, | ||
assert, | ||
ByteString, | ||
byteString2Int, | ||
FixedArray, | ||
hash256, | ||
lshift, | ||
method, | ||
prop, | ||
SmartContract, | ||
} from 'scrypt-ts' | ||
|
||
// 512 * 256 bit random byte strings | ||
export type LamportPubKey = FixedArray<ByteString, 512> | ||
|
||
// For msg of 256 bits. | ||
export type LamportSig = FixedArray<ByteString, 256> | ||
|
||
export class LamportP2PK extends SmartContract { | ||
@prop() | ||
pubKey: LamportPubKey | ||
|
||
constructor(pubKey: LamportPubKey) { | ||
super(...arguments) | ||
this.pubKey = pubKey | ||
} | ||
|
||
@method() | ||
public unlock(sig: LamportSig) { | ||
const m = byteString2Int(hash256(this.ctx.serialize())) | ||
|
||
// Loop over each bit of the message. | ||
for (let i = 0; i < 256; i++) { | ||
let offset = 0n | ||
if (and(lshift(m, BigInt(i)), 1n) == 0n) { | ||
offset = 256n | ||
} | ||
|
||
const sigChunk = sig[i] | ||
const pkChunk = this.pubKey[Number(offset) + i] | ||
assert(hash256(sigChunk) == pkChunk, `sig chunk ${i} hash mismatch`) | ||
} | ||
} | ||
} |
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,132 @@ | ||
import { expect, use } from 'chai' | ||
import { | ||
ByteString, | ||
FixedArray, | ||
MethodCallOptions, | ||
bsv, | ||
byteString2Int, | ||
fill, | ||
hash256, | ||
lshift, | ||
sha256, | ||
toByteString, | ||
} from 'scrypt-ts' | ||
import { | ||
LamportP2PK, | ||
LamportPubKey, | ||
LamportSig, | ||
} from '../src/contracts/lamportSig' | ||
import { getDefaultSigner } from './utils/helper' | ||
import chaiAsPromised from 'chai-as-promised' | ||
import { and, getPreimage } from 'scryptlib' | ||
use(chaiAsPromised) | ||
|
||
type LamportSecretKey = FixedArray<ByteString, 512> | ||
|
||
describe('Heavy: Test SmartContract `LamportSig`', () => { | ||
let sk: LamportSecretKey | ||
let pk: LamportPubKey | ||
|
||
let instance: LamportP2PK | ||
|
||
before(async () => { | ||
await LamportP2PK.loadArtifact() | ||
|
||
sk = fill(bsv.PrivateKey.fromRandom().toByteString(), 512) | ||
pk = fill(toByteString(''), 512) | ||
for (let i = 0; i < 512; i++) { | ||
pk[i] = hash256(sk[i]) | ||
} | ||
|
||
instance = new LamportP2PK(pk) | ||
await instance.connect(getDefaultSigner()) | ||
}) | ||
|
||
it('should pass the public method unit test successfully.', async () => { | ||
const paymentAmt = 100 | ||
|
||
const deployTx = await instance.deploy(paymentAmt) | ||
console.log(`Deployed contract "LamportSig": ${deployTx.id}`) | ||
|
||
// Create unsigned TX to get sighHash preimage. | ||
const dummySig: LamportSig = fill(toByteString(''), 256) | ||
const dummyCallRes = await instance.methods.unlock(dummySig, { | ||
partiallySigned: true, | ||
exec: false, | ||
autoPayFee: false, | ||
} as MethodCallOptions<LamportP2PK>) | ||
|
||
// Sign tx. | ||
const sig: LamportSig = fill(toByteString(''), 256) | ||
const txSigHashPreimage = getPreimage( | ||
dummyCallRes.tx, | ||
instance.lockingScript, | ||
paymentAmt, | ||
0 | ||
) | ||
const m = byteString2Int(hash256(txSigHashPreimage)) | ||
for (let i = 0; i < 256; i++) { | ||
let offset = 0n | ||
if (and(lshift(m, BigInt(i)), 1n) == 0n) { | ||
offset = 256n | ||
} | ||
sig[i] = sk[Number(offset) + i] | ||
} | ||
|
||
// Execute actual contract call. | ||
const call = async () => { | ||
const callRes = await instance.methods.unlock(sig, { | ||
partiallySigned: true, | ||
autoPayFee: false, | ||
} as MethodCallOptions<LamportP2PK>) | ||
|
||
console.log(`Called "unlock" method: ${callRes.tx.id}`) | ||
} | ||
await expect(call()).not.to.be.rejected | ||
}) | ||
|
||
it('should throw with wrong sig.', async () => { | ||
const paymentAmt = 100 | ||
|
||
const deployTx = await instance.deploy(paymentAmt) | ||
console.log(`Deployed contract "LamportSig": ${deployTx.id}`) | ||
|
||
// Create unsigned TX to get sighHash preimage. | ||
const dummySig: LamportSig = fill(toByteString(''), 256) | ||
const dummyCallRes = await instance.methods.unlock(dummySig, { | ||
partiallySigned: true, | ||
exec: false, | ||
autoPayFee: false, | ||
} as MethodCallOptions<LamportP2PK>) | ||
|
||
// Sign tx. | ||
const sig: LamportSig = fill(toByteString(''), 256) | ||
const txSigHashPreimage = getPreimage( | ||
dummyCallRes.tx, | ||
instance.lockingScript, | ||
paymentAmt, | ||
0 | ||
) | ||
const m = byteString2Int(hash256(txSigHashPreimage)) | ||
for (let i = 0; i < 256; i++) { | ||
let offset = 0n | ||
if (and(lshift(m, BigInt(i)), 1n) == 0n) { | ||
offset = 256n | ||
} | ||
sig[i] = sk[Number(offset) + i] | ||
} | ||
|
||
sig[3] = toByteString(hash256(toByteString('wrong data'))) | ||
|
||
// Execute actual contract call. | ||
const call = async () => { | ||
const callRes = await instance.methods.unlock(sig, { | ||
partiallySigned: true, | ||
autoPayFee: false, | ||
} as MethodCallOptions<LamportP2PK>) | ||
|
||
console.log(`Called "unlock" method: ${callRes.tx.id}`) | ||
} | ||
await expect(call()).to.be.rejectedWith(/sig chunk 3 hash mismatch/) | ||
}) | ||
}) |